This commit is contained in:
Gaurav Kumar
2025-11-08 11:52:59 +05:30
parent bee89fc12b
commit 97ea7c3554
285 changed files with 4705 additions and 60672 deletions

View File

@@ -1,9 +1,8 @@
export class ReportBuilder {
public report_id: number;
public report_name: string;
public description: string;
public report_tags: string;
public servicename: string;
// Add SureConnect reference
public sureConnectId: number | null;
public report_name:string;
public description: string;
public report_tags: string;
public servicename:string;
}

View File

@@ -23,39 +23,6 @@ export interface DashboardContentModel {
component?: any;
name: string;
type?:string;
// 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;
datasource?: string;
fieldName?: string;
connection?: string;
baseFilters?: any[];
// Common filter properties
commonFilterEnabled?: boolean;
// Drilldown properties
drilldownEnabled?: boolean;
drilldownApiUrl?: string;
drilldownXAxis?: string;
drilldownYAxis?: string;
drilldownParameter?: string;
drilldownFilters?: any[];
drilldownLayers?: any[];
// Compact filter properties
filterKey?: string;
filterType?: string;
filterLabel?: string;
filterOptions?: string[];
}
export interface DashboardModel {
@@ -100,10 +67,6 @@ export class value1{
export const WidgetsMock: WidgetModel[] = [
{
name: 'Common Filter',
identifier: 'common_filter'
},
{
name: 'Radar Chart',
identifier: 'radar_chart'

View File

@@ -1,155 +1,39 @@
<!-- Modern AI-Inspired Login Page -->
<div class="modern-login-container" *ngIf="showLogin2">
<!-- Background Elements -->
<div class="login-background">
<div class="gradient-overlay"></div>
<div class="floating-shapes">
<div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
<div class="shape shape-4"></div>
</div>
</div>
<!-- <router-outlet></router-outlet> -->
<!-- Main Login Content -->
<div class="login-content">
<!-- Left Side - Branding -->
<div class="login-branding">
<div class="brand-content">
<div class="logo-section">
<div class="logo-container">
<img class="logo-img" src="assets/images/icon/micrologo.png" alt="CloudnSure Logo">
<div class="logo-glow"></div>
</div>
<h1 class="brand-title">
<ng-container *ngIf="loginEnvironment?.companyName; else defaultTitle">
<!-- <div class="login-wrapper" *ngIf="showLogin2 && loginEnvironment.templateNo == 'Template 1'" [style.backgroundImage]="'url(' + loginEnvironment.imagePath + ')'">
<form class="login">
<section class="title">
<h3 class="welcome">Welcome to</h3>
{{ loginEnvironment.companyName }}
</ng-container>
<ng-template #defaultTitle>
CloudnSure
</ng-template>
</h1>
<p class="brand-subtitle">Enterprise Platform</p>
<h5 class="hint">Use your ID to sign in OR <a [routerLink]="['/email-verification']" class="signup">create one now </a> </h5>
</section>
<div class="login-group">
<clr-input-container>
<label class="clr-sr-only">Username</label>
<input type="text" name="username" clrInput autocomplete="off" [(ngModel)]="model.email"
placeholder="Username (sysadmin)"/>
</clr-input-container>
<clr-password-container>
<label class="clr-sr-only">Password</label>
<input type="password" name="password" clrPassword id="login_password" autocomplete="off" [(ngModel)]="model.password"
placeholder="Password (test3)"/>
</clr-password-container>
<clr-checkbox-wrapper>
<input clrCheckbox type="checkbox" id="rememberme">
<label for="rememberme"> Remember me </label>
</clr-checkbox-wrapper>
<div class="error active" *ngIf="isError">
Invalid user name or password
</div>
<div class="brand-features">
<div class="feature-item">
<clr-icon shape="shield" class="feature-icon"></clr-icon>
<span>Secure & Reliable</span>
</div>
<div class="feature-item">
<clr-icon shape="analytics" class="feature-icon"></clr-icon>
<span>Advanced Analytics</span>
</div>
<div class="feature-item">
<clr-icon shape="cog" class="feature-icon"></clr-icon>
<span>Customizable</span>
</div>
</div>
</div>
</div>
<!-- Right Side - Login Form -->
<div class="login-form-section">
<div class="form-container">
<div class="form-header">
<h2 class="form-title">Welcome Back</h2>
<p class="form-subtitle">Sign in to your account to continue</p>
</div>
<form class="modern-login-form" (ngSubmit)="onLogin()">
<!-- Username Field -->
<div class="form-group">
<label class="form-label">Username</label>
<div class="input-container">
<clr-icon shape="user" class="input-icon"></clr-icon>
<input type="text" name="username" class="modern-input" autocomplete="username" [(ngModel)]="model.email"
(input)="clearError()" placeholder="Enter your username" required>
</div>
</div>
<!-- Password Field -->
<div class="form-group">
<label class="form-label">Password</label>
<div class="input-container">
<clr-icon shape="lock" class="input-icon"></clr-icon>
<input [type]="showPassword ? 'text' : 'password'" name="password" class="modern-input"
autocomplete="current-password" [(ngModel)]="model.password" (input)="clearError()"
placeholder="Enter your password" required>
<button type="button" class="password-toggle" (click)="togglePasswordVisibility()"
[attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
<span class="toggle-text">{{ showPassword ? 'Hide' : 'Show' }}</span>
</button>
</div>
</div>
<!-- Remember Me & Forgot Password -->
<div class="form-options">
<label class="checkbox-container">
<input type="checkbox" id="rememberme" class="modern-checkbox">
<span class="checkmark"></span>
<span class="checkbox-label">Remember me</span>
</label>
<a href="javascript://" class="forgot-link" (click)="goforgotpass()">
Forgot password?
</a>
</div>
<!-- Error Message -->
<div class="error-message" *ngIf="isError">
<clr-icon shape="exclamation-triangle"></clr-icon>
<span>{{ errMsg || 'Invalid username or password' }}</span>
</div>
<!-- Login Button -->
<button type="submit" class="modern-login-btn" [disabled]="isLoading" [class.loading]="isLoading">
<span class="btn-content" *ngIf="!isLoading">
<clr-icon shape="login"></clr-icon>
Sign In
</span>
<span class="btn-loading" *ngIf="isLoading">
<div class="spinner"></div>
Signing In...
</span>
</button>
<!-- Sign Up Link -->
<div class="signup-section" *ngIf="loginEnvironment?.isSignup === 'true'">
<p class="signup-text">
Don't have an account?
<a [routerLink]="['/email-verification']" class="signup-link">
Create one now
</a>
</p>
<button [disabled]="!model.email || !model.password" type="submit" class="btn btn-primary"
(click)="onLogin()">Login</button>
<a (click)="goforgotpass()" class="signup">{{ loginEnvironment.forgotpassword }}</a>
</div>
</form>
</div> -->
<!-- Social Login (Optional) -->
<div class="social-login">
<div class="divider">
<span>Or continue with</span>
</div>
<div class="social-buttons">
<button class="social-btn google-btn" type="button">
<clr-icon shape="world"></clr-icon>
<span>Google</span>
</button>
<button class="social-btn microsoft-btn" type="button">
<clr-icon shape="world"></clr-icon>
<span>Microsoft</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!--
<div [class]="getWrapperClass()" *ngIf="showLogin2 && loginEnvironment.templateNo == '<templateNo>'" [style.backgroundImage]="'url(' + ('../../../../assets/images/new.png') + ')'">
<form class="login">
<section class="title">
@@ -224,7 +108,7 @@
<!-- <div class="login-wrapper"> -->
<div [class]="getWrapperClass()" *ngIf="showLogin2 && loginEnvironment.templateNo == 'Template 2'" [style.backgroundImage]="'url(' + (loginEnvironment.loginImageURL !== 'null' || null ? loginEnvironment.loginImageURL : '../../../../assets/images/new.png') + ')'">
<form class="login">
<section class="title">
@@ -259,4 +143,4 @@
</div>
</form>
</div>
-->

View File

@@ -1,642 +1,12 @@
// ========================================
// MODERN AI-INSPIRED LOGIN PAGE STYLES
// ========================================
@import '../../../../styles/design-tokens';
// Modern Login Container
.modern-login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, $primary-50 0%, $primary-100 50%, $secondary-50 100%);
}
// Background Elements
.login-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
overflow: hidden;
.gradient-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg,
rgba($primary-600, 0.1) 0%,
rgba($primary-400, 0.05) 50%,
rgba($accent-purple, 0.1) 100%);
}
.floating-shapes {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
.shape {
position: absolute;
border-radius: 50%;
background: linear-gradient(45deg, rgba($primary-400, 0.1), rgba($accent-purple, 0.1));
animation: float 6s ease-in-out infinite;
&.shape-1 {
width: 200px;
height: 200px;
top: 10%;
left: 10%;
animation-delay: 0s;
}
&.shape-2 {
width: 150px;
height: 150px;
top: 60%;
right: 15%;
animation-delay: 2s;
}
&.shape-3 {
width: 100px;
height: 100px;
bottom: 20%;
left: 20%;
animation-delay: 4s;
}
&.shape-4 {
width: 80px;
height: 80px;
top: 30%;
right: 30%;
animation-delay: 1s;
}
}
}
}
// Main Content
.login-content {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: 1fr 1fr;
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: $space-8;
gap: $space-12;
align-items: center;
}
// Left Side - Branding
.login-branding {
display: flex;
align-items: center;
justify-content: center;
padding: $space-8;
.brand-content {
text-align: center;
max-width: 400px;
}
.logo-section {
margin-bottom: $space-8;
.logo-container {
position: relative;
display: inline-block;
margin-bottom: $space-6;
.logo-img {
width: 80px;
height: 80px;
object-fit: contain;
filter: drop-shadow(0 8px 16px rgba($primary-600, 0.3));
transition: transform $duration-300 $ease-out;
}
.logo-glow {
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background: linear-gradient(45deg, $primary-400, $accent-purple);
border-radius: 50%;
opacity: 0;
filter: blur(20px);
transition: opacity $duration-300 $ease-out;
}
&:hover {
.logo-img {
transform: scale(1.1);
}
.logo-glow {
opacity: 0.3;
}
}
}
.brand-title {
font-size: $text-4xl;
font-weight: $font-bold;
color: $gray-900;
margin-bottom: $space-2;
background: linear-gradient(135deg, $primary-600, $accent-purple);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.brand-subtitle {
font-size: $text-lg;
color: $gray-600;
font-weight: $font-medium;
}
}
.brand-features {
display: flex;
flex-direction: column;
gap: $space-4;
.feature-item {
display: flex;
align-items: center;
gap: $space-3;
padding: $space-3;
background: rgba(255, 255, 255, 0.8);
border-radius: $radius-lg;
backdrop-filter: $backdrop-blur-sm;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all $duration-200 $ease-out;
&:hover {
transform: translateX(8px);
background: rgba(255, 255, 255, 0.9);
box-shadow: $shadow-md;
}
.feature-icon {
color: $primary-600;
font-size: $text-lg;
}
span {
font-size: $text-sm;
font-weight: $font-medium;
color: $gray-700;
}
}
}
}
// Right Side - Login Form
.login-form-section {
display: flex;
align-items: center;
justify-content: center;
padding: $space-8;
.form-container {
width: 100%;
max-width: 400px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: $backdrop-blur-md;
border-radius: $radius-2xl;
padding: $space-8;
box-shadow: $shadow-xl;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.form-header {
text-align: center;
margin-bottom: $space-8;
.form-title {
font-size: $text-2xl;
font-weight: $font-bold;
color: $gray-900;
margin-bottom: $space-2;
}
.form-subtitle {
font-size: $text-sm;
color: $gray-600;
}
}
.modern-login-form {
.form-group {
margin-bottom: $space-6;
.form-label {
display: block;
font-size: $text-sm;
font-weight: $font-semibold;
color: $gray-700;
margin-bottom: $space-2;
}
.input-container {
position: relative;
display: flex;
align-items: center;
.input-icon {
position: absolute;
left: $space-3;
color: $gray-400;
font-size: $text-base;
z-index: 2;
}
.modern-input {
width: 100%;
padding: $space-3 $space-3 $space-3 $space-10;
border: 2px solid $gray-200;
border-radius: $radius-lg;
font-size: $text-sm;
font-weight: $font-medium;
color: $gray-900;
background: $white;
transition: all $duration-200 $ease-out;
&::placeholder {
color: $gray-400;
}
&:focus {
outline: none;
border-color: $primary-500;
box-shadow: 0 0 0 3px rgba($primary-500, 0.1);
}
&:hover {
border-color: $gray-300;
}
}
// Password field with toggle button
&:has(.password-toggle) .modern-input {
padding-right: $space-10;
}
.password-toggle {
position: absolute;
right: $space-3;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: $gray-400;
cursor: pointer;
padding: $space-1;
border-radius: $radius-sm;
transition: all $duration-200 $ease-out;
z-index: 2;
&:hover {
color: $gray-600;
background: rgba($gray-100, 0.5);
}
&:focus {
outline: none;
color: $primary-600;
background: rgba($primary-50, 0.5);
}
clr-icon {
font-size: $text-base;
}
.toggle-text {
font-size: $text-xs;
font-weight: $font-semibold;
color: inherit;
text-transform: uppercase;
letter-spacing: 0.05em;
}
}
}
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-6;
.checkbox-container {
display: flex;
align-items: center;
gap: $space-2;
cursor: pointer;
.modern-checkbox {
width: 18px;
height: 18px;
accent-color: $primary-600;
}
.checkbox-label {
font-size: $text-sm;
color: $gray-600;
font-weight: $font-medium;
}
}
.forgot-link {
font-size: $text-sm;
color: $primary-600;
text-decoration: none;
font-weight: $font-medium;
transition: color $duration-200 $ease-out;
&:hover {
color: $primary-700;
}
}
}
.error-message {
display: flex;
align-items: center;
gap: $space-2;
padding: $space-3;
background: rgba($accent-red, 0.1);
border: 1px solid rgba($accent-red, 0.2);
border-radius: $radius-lg;
margin-bottom: $space-6;
color: $accent-red;
font-size: $text-sm;
font-weight: $font-medium;
clr-icon {
font-size: $text-base;
}
}
.modern-login-btn {
width: 100%;
padding: $space-4;
background: linear-gradient(135deg, $primary-600, $primary-700);
color: $white;
border: none;
border-radius: $radius-lg;
font-size: $text-base;
font-weight: $font-semibold;
cursor: pointer;
transition: all $duration-200 $ease-out;
position: relative;
overflow: hidden;
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: $shadow-lg;
background: linear-gradient(135deg, $primary-700, $primary-800);
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
&.loading {
.btn-content {
opacity: 0;
}
.btn-loading {
opacity: 1;
}
}
.btn-content {
display: flex;
align-items: center;
justify-content: center;
gap: $space-2;
transition: opacity $duration-200 $ease-out;
}
.btn-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
gap: $space-2;
opacity: 0;
transition: opacity $duration-200 $ease-out;
.spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top: 2px solid $white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
}
}
.signup-section {
text-align: center;
margin-top: $space-6;
.signup-text {
font-size: $text-sm;
color: $gray-600;
.signup-link {
color: $primary-600;
text-decoration: none;
font-weight: $font-semibold;
transition: color $duration-200 $ease-out;
&:hover {
color: $primary-700;
}
}
}
}
}
.social-login {
margin-top: $space-8;
.divider {
position: relative;
text-align: center;
margin-bottom: $space-6;
&::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: $gray-200;
}
span {
background: rgba(255, 255, 255, 0.95);
padding: 0 $space-4;
font-size: $text-xs;
color: $gray-500;
font-weight: $font-medium;
}
}
.social-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $space-3;
.social-btn {
display: flex;
align-items: center;
justify-content: center;
gap: $space-2;
padding: $space-3;
border: 2px solid $gray-200;
border-radius: $radius-lg;
background: $white;
color: $gray-700;
font-size: $text-sm;
font-weight: $font-medium;
cursor: pointer;
transition: all $duration-200 $ease-out;
&:hover {
border-color: $gray-300;
transform: translateY(-1px);
box-shadow: $shadow-md;
}
&.google-btn:hover {
border-color: #db4437;
color: #db4437;
}
&.microsoft-btn:hover {
border-color: #0078d4;
color: #0078d4;
}
clr-icon {
font-size: $text-base;
}
}
}
}
}
// Animations
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
// Responsive Design
@media (max-width: $breakpoint-lg) {
.login-content {
grid-template-columns: 1fr;
gap: $space-8;
padding: $space-6;
}
.login-branding {
order: 2;
padding: $space-4;
.brand-content {
max-width: 100%;
}
.logo-section .brand-title {
font-size: $text-3xl;
}
.brand-features {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
}
.login-form-section {
order: 1;
padding: $space-4;
.form-container {
padding: $space-6;
}
}
}
@media (max-width: $breakpoint-md) {
.modern-login-container {
padding: $space-4;
}
.login-content {
padding: $space-4;
}
.login-branding {
.brand-features {
flex-direction: column;
}
}
.login-form-section .form-container {
padding: $space-4;
}
}
// Legacy styles (preserved for compatibility)
.tamplate1 {
.tamplate1 {
background-image: url(../../../../assets/images/new.png);
background-repeat: no-repeat;
background-size: 60%;
background-position: right center;
border: 1px solid #000;
}
}
.tamplate2 {
.tamplate2 {
background-image: url(../../../../assets/images/new.png);
background-repeat: no-repeat;
background-size: 60%;
@@ -645,9 +15,9 @@
justify-content: flex-end;
align-items: center;
border: 1px solid #000;
}
.signup {
}
.signup {
cursor: pointer;
}
// color:#80b0ff;
}

View File

@@ -41,8 +41,6 @@ export class LoginPageComponent implements OnInit {
email = '';
password = '';
isError = false;
isLoading = false;
showPassword = false;
model: any = {};
errMsg: string = '';
@@ -73,80 +71,32 @@ export class LoginPageComponent implements OnInit {
}
onLogin() {
console.log('onLogin called');
console.log('Form data:', { email: this.model.email, password: this.model.password });
// Reset error state
this.isError = false;
this.errMsg = '';
// Validate inputs
if (!this.model.email || !this.model.password) {
this.isError = true;
this.errMsg = 'Please enter both email and password';
console.log('Validation failed - missing email or password');
return;
}
// Set loading state
this.isLoading = true;
console.log('Loading state set to true, making API call...');
// tslint:disable-next-line:max-line-length
this.loginService.getToken(this.model.email, this.model.password)
.subscribe(
resp => {
console.log('API Response received:', resp);
this.isLoading = false;
// Handle different response formats
if (resp.operationStatus === 'ERROR') {
this.isError = true;
this.errMsg = resp.operationMessage || 'Login failed';
return;
}
// Handle different response formats
if (resp.success === 'false') {
this.isError = true;
this.errMsg = resp.message || 'Login failed';
return;
}
.subscribe(resp => {
if (resp.user === undefined || resp.user.token === undefined || resp.user.token === "INVALID") {
this.isError = true;
this.errMsg = 'Invalid email or password';
this.errMsg = 'Checking Email or password';
return;
}
// Success - navigate to landing page
console.log('Login successful, navigating to:', resp.landingPage);
this.router.navigate([resp.landingPage]);
this.router.navigate([resp.landingPage]);// add , {skipLocationChange: true}
},
(errResponse: HttpErrorResponse) => {
console.log('API Error received:', errResponse);
this.isLoading = false;
this.isError = true;
switch (errResponse.status) {
case 401:
this.errMsg = 'Email or password is incorrect';
break;
case 404:
this.errMsg = 'Service not found';
break;
case 408:
this.errMsg = 'Request timeout';
break;
this.errMsg = 'Request Timedout';
case 500:
this.errMsg = 'Internal server error';
break;
case 0:
this.errMsg = 'Network error - please check your connection';
break;
this.errMsg = 'Internal Server Error';
default:
this.errMsg = 'An error occurred. Please try again.';
this.errMsg = 'Server Error';
}
}
);
}
goaccount(){
}
@@ -154,17 +104,5 @@ export class LoginPageComponent implements OnInit {
this.router.navigate(["../forgotpass"], { relativeTo: this.route });
}
// Clear error when user starts typing
clearError() {
if (this.isError) {
this.isError = false;
this.errMsg = '';
}
}
// Toggle password visibility
togglePasswordVisibility() {
this.showPassword = !this.showPassword;
}
}

View File

@@ -12,6 +12,12 @@
@@ -234,8 +240,7 @@ export class Stepper_workflowComponent implements OnInit {
rsModaldescription = false;
goToReplaceStringdescription(row) {
this.rowSelected = row;
this.rsModaldescription = true;
this.rowSelected = row; this.rsModaldescription = true;
}
@@ -248,39 +253,6 @@ export class Stepper_workflowComponent implements OnInit {
this.router.navigate(["../demostepper/", id], { relativeTo: this.route });
}
// updateaction

View File

@@ -1,465 +1,279 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon> {{'SECURITY' | translate}}</a></li>
<li><a href="javascript://">{{'MENU_ACCESS_CONTROL' | translate}}</a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon>{{'SECURITY' | translate}}</a></li>
<li><a href="javascript://">{{ 'MENU_ACCESS_CONTROL' | translate }}</a></li>
</ol>
<section class="mac-hero">
<div class="mac-hero__content">
<div class="mac-hero__icon">
<clr-icon shape="shield-check"></clr-icon>
<div class="clr-row">
<div class="clr-col-6" style="display:flex ;">
<div style="margin-right: 25px;">
<h4 style="font-weight:300;">{{ 'MENU_ACCESS_CONTROL' | translate }} </h4>
</div>
<div>
<h2 class="mac-hero__title">{{'MENU_ACCESS_CONTROL' | translate}}</h2>
<p class="mac-hero__subtitle">{{'SECURITY' | translate}}</p>
<div style="margin-top:27px ; position: relative;">
<span class="label label-light-blue" style="display: inline;">{{ 'EDIT_MODE' | translate }}</span>
</div>
</div>
<div class="mac-hero__actions">
<span class="mac-chip">{{'EDIT_MODE' | translate}}</span>
<div class="mac-sep"></div>
<label class="mac-label">{{'FOR' | translate}}</label>
<select class="mac-select" (change)="idofselected($event.target.value)">
<div class="clr-col-6" style="text-align: right;">
{{ 'FOR' | translate }} <select id="" style="height: 30px;" (change)="idofselected($event.target.value)">
<option *ngFor="let sub of givendata" [value]="sub.usrGrp" [selected]="sub.usrGrp== 40">{{sub.groupName}}</option>
</select>
<button id="add" class="mac-btn mac-btn-primary" (click)="getbyuseriddata()">
<clr-icon shape="refresh"></clr-icon>
<span class="mac-btn-text">{{'RELOAD' | translate}}</span>
</button>
<div class="mac-toggle">
<span class="mac-toggle__label" *ngIf="!toggle">{{'SHOW_ALL' | translate}}</span>
<span class="mac-toggle__label" *ngIf="toggle">{{'ONLY_MAIN_MENU' | translate}}</span>
<button id="add" class="btn btn-primary" style="margin-left: 20px;" (click)="getbyuseriddata()"> {{ 'RELOAD' |
translate }} </button>
<span *ngIf="!toggle">{{ 'SHOW_ALL' | translate }} </span><span *ngIf="toggle">{{ 'ONLY_MAIN_MENU' | translate
}}</span>
<label class="switch">
<input type="checkbox" ([ngModel])="toggle" checked (click)="toggleCheckbox()">
<span class="slider round"></span>
</label>
</div>
</div>
</section>
</div>
<hr>
<div class="mac-container">
<div class="dg-wrapper">
<div class="clr-row">
<div class="clr-col-12 mac-actions">
<button id="add" class="mac-btn mac-btn-primary" (click)="modaladd()" [disabled]="!toggle">
<clr-icon shape="plus"></clr-icon>
<span>{{'ADD' | translate}}</span>
</button>
<div *ngIf="!toggle" class="error-message mac-hint">
{{'CLOSE_TOGGLE_TO_ACTIVATE_ADD_BUTTON' | translate}}
<div class="clr-col-12" style="text-align: right;">
<button id="add" class="btn btn-primary" style="margin-left: 20px;" (click)="modaladd()" [disabled]="!toggle">
<clr-icon shape="plus"></clr-icon>{{ 'ADD' | translate }} </button>
<div *ngIf="!toggle" class="error-message" style="color: red; font-size: 14px; margin-top: 5px;">
{{ ' Close toggle to activate Add button. ' | translate }}
</div>
</div>
</div>
<div class="mac-toolbar">
<div class="mac-toolbar__left">
<div class="mac-search">
<clr-icon shape="search"></clr-icon>
<input class="mac-search__input" type="text" placeholder="{{'SEARCH' | translate}}" [(ngModel)]="filterText" />
</div>
</div>
<div class="mac-toolbar__right mac-stats">
<div class="mac-stat">
<div class="mac-stat__value">{{ totalItems }}</div>
<div class="mac-stat__label">{{'TOTAL_ITEMS' | translate}}</div>
</div>
<div class="mac-stat">
<div class="mac-stat__value">{{ mainMenuCount }}</div>
<div class="mac-stat__label">{{'MAIN_MENUS' | translate}}</div>
</div>
<div class="mac-stat">
<div class="mac-stat__value">{{ subMenuCount }}</div>
<div class="mac-stat__label">{{'SUB_MENUS' | translate}}</div>
</div>
</div>
</div>
<clr-datagrid class="mac-grid" [clrDgLoading]="loading" *ngIf="!toggle">
<clr-datagrid [clrDgLoading]="loading" *ngIf="!toggle">
<clr-dg-placeholder>
<ng-template #loadingSpinner>
<clr-spinner>{{'LOADING' | translate}} ... </clr-spinner>
</ng-template>
<div *ngIf="alldata?.length === 0;else loadingSpinner">{{'NO_DATA_AVAILABLE' | translate}}</div>
<ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
<div *ngIf="alldata?.length === 0;else loadingSpinner">{{ 'NO_DATA_AVAILABLE' | translate }}</div>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'no'" class="mac-col mac-col--no">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="hashtag"></clr-icon> {{'NO' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'menuname'" class="mac-col mac-col--name">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="tag"></clr-icon> {{'MENU_ITEM_NAME' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mvisible'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="eye"></clr-icon> {{'VIEW' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mcreate'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="plus"></clr-icon> {{'CREATE' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'medit'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="edit"></clr-icon> {{'EDIT' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mdelete'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="trash"></clr-icon> {{'DELETE' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mquery'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="search"></clr-icon> {{'QUERY' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mexport'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="export"></clr-icon> {{'EXPORT' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'no'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'NO' | translate }}
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'menuname'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'MENU_ITEM_NAME' | translate }}
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mvisible'"><ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'VIEW' | translate }}
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mcreate'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'CREATE' | translate }}
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'medit'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'EDIT' | translate }}
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mdelete'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'DELETE' | translate }}
<!-- <input type="checkbox" clrCheckbox name="mdelete" [checked]="colvalue" (change)="changedelete($event.target.checked)"> -->
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mquery'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'QUERY' | translate }}
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mexport'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'EXPORT' | translate }}
</ng-container></clr-dg-column>
<clr-dg-row *ngFor="let all of filteredAllData;let i=index">
<clr-dg-row *ngFor="let all of alldata;let i=index">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell *ngIf="all.menuId==0">
<b>{{all.menuItemDesc}}</b>
</clr-dg-cell>
<clr-dg-cell *ngIf="all.menuId==0"><b>{{all.menuItemDesc}}</b></clr-dg-cell>
<clr-dg-cell *ngIf="all.menuId!==0">&nbsp;&nbsp;&nbsp;{{all.menuItemDesc}}</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mvisible">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mcreate">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.medit">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mdelete">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mquery">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mexport">
</clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mvisible"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mcreate"></clr-dg-cell>
<clr-dg-cell> <input type="checkbox" [checked]="selected ===all.medit"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mdelete"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mquery"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mexport"></clr-dg-cell>
<clr-dg-action-overflow>
<button class="mac-action-item" (click)="modalEdit(all)">
{{'EDIT' | translate}}
<clr-icon shape="edit" class="is-error"></clr-icon>
</button>
<button class="mac-action-item" *ngIf="all.menuId!==0" (click)="modalDelete(all)">
{{'DELETE' | translate}}
<clr-icon shape="trash" class="is-error"></clr-icon>
</button>
<button class="mac-action-item" *ngIf="all.menuId==0" (click)="modaldeletemainmenu(all)">
{{'DELETE_MENU_SUBMENU' | translate}}
<clr-icon shape="edit" class="is-error"></clr-icon>
</button>
<button class="action-item" (click)="modalEdit(all)">{{ 'EDIT' | translate }} <clr-icon shape="edit"
class="is-error"></clr-icon></button>
<button class="action-item" *ngIf="all.menuId!==0" (click)="modalDelete(all)">{{ 'DELETE' | translate }}
<clr-icon shape="trash" class="is-error"></clr-icon></button>
<button class="action-item" *ngIf="all.menuId==0" (click)="modaldeletemainmenu(all)">{{ 'DELETE_MENU_SUBMENU' |
translate }}
<clr-icon shape="edit" class="is-error"></clr-icon></button>
</clr-dg-action-overflow>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">{{'MENUNAME' | translate}}</td>
<td class="td-title">Menuname</td>
<td class="td-content"></td>
</tr>
</table>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer class="mac-grid-footer">
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{'RECORDS_PER_PAGE' | translate}}</clr-dg-page-size>
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Record per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
{{'OF' | translate}} {{pagination.totalItems}} {{'RECORDS' | translate}}
of {{pagination.totalItems}} Records
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<clr-datagrid class="mac-grid" [clrDgLoading]="loading" *ngIf="toggle">
<clr-datagrid [clrDgLoading]="loading" *ngIf="toggle">
<clr-dg-placeholder>
<ng-template #loadingSpinner>
<clr-spinner>{{'LOADING' | translate}} ... </clr-spinner>
</ng-template>
<div *ngIf="alldata?.length === 0;else loadingSpinner">{{'NO_DATA_AVAILABLE' | translate}}</div>
<ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
<div *ngIf="alldata?.length === 0;else loadingSpinner">No data available</div>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'no'" class="mac-col mac-col--no">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="hashtag"></clr-icon> {{'NO' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'menuname'" class="mac-col mac-col--name">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="tag"></clr-icon> {{'MENU_ITEM_NAME' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mvisible'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="eye"></clr-icon> {{'VIEW' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mcreate'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="plus"></clr-icon> {{'CREATE' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'medit'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="edit"></clr-icon> {{'EDIT' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mdelete'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="trash"></clr-icon> {{'DELETE' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mquery'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="search"></clr-icon> {{'QUERY' | translate}}
</span>
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'mexport'" class="mac-col mac-col--perm">
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mac-col-title">
<clr-icon shape="export"></clr-icon> {{'EXPORT' | translate}}
</span>
</ng-container>
</clr-dg-column>
<ng-container *ngFor="let all of filteredAllData;let i=index">
<clr-dg-column [clrDgField]="'no'"> <ng-container *clrDgHideableColumn="{hidden: false}">
NO
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'menuname'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Menu Item Name
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mvisible'"><ng-container *clrDgHideableColumn="{hidden: false}">
View
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mcreate'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Create
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'medit'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Edit
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mdelete'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Delete
<!-- <input type="checkbox" clrCheckbox name="mdelete" [checked]="colvalue" (change)="changedelete($event.target.checked)"> -->
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mquery'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Query
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'mexport'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Export
</ng-container></clr-dg-column>
<ng-container *ngFor="let all of alldata;let i=index">
<clr-dg-row *ngIf="all.menuId==0">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell>
<b>{{all.menuItemDesc}}</b>
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mvisible">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mcreate">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.medit">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mdelete">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mquery">
</clr-dg-cell>
<clr-dg-cell>
<input type="checkbox" [checked]="selected ===all.mexport">
</clr-dg-cell>
<clr-dg-cell><b>{{all.menuItemDesc}}</b></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mvisible"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mcreate"></clr-dg-cell>
<clr-dg-cell> <input type="checkbox" [checked]="selected ===all.medit"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mdelete"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mquery"></clr-dg-cell>
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mexport"></clr-dg-cell>
<clr-dg-action-overflow>
<button class="mac-action-item" (click)="Sync(all.menuItemId?.menuItemId,all)">
{{'SYNC' | translate}}
<clr-icon shape="shrink" class="is-error"></clr-icon>
</button>
<button class="action-item" (click)="Sync(all.menuItemId?.menuItemId,all)">Sync <clr-icon shape="shrink"
class="is-error"></clr-icon> </button>
</clr-dg-action-overflow>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">{{'MENUNAME' | translate}}</td>
<td class="td-title">Menuname</td>
<td class="td-content"></td>
</tr>
</table>
</clr-dg-row-detail>
</clr-dg-row>
</ng-container>
<clr-dg-footer class="mac-grid-footer">
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{'RECORDS_PER_PAGE' | translate}}</clr-dg-page-size>
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Record per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
{{'OF' | translate}} {{pagination.totalItems}} {{'RECORDS' | translate}}
of {{pagination.totalItems}} Records
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<clr-modal class="mac-modal" [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title mac-modal-title">
<clr-icon shape="plus"></clr-icon> {{'ADD_MENU_ACCESS_CONTROL' | translate}}
</h3>
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Add Menu Access Control</h3>
<div class="modal-body">
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
<div class="clr-row">
<div class="clr-col-sm-12 mac-form-group">
<label class="mac-form-label" for="name">{{'MENU_NAME' | translate}}
<span class="required-field">*</span>
</label>
<select name="" id="" class="mac-form-select w-100" (change)="idselected($event.target.value)">
<div class="clr-col-sm-12">
<label for="name">Menu Name<span class="required-field">*</span></label>
<select name="" id="" style="width: 100%;padding: 5px 5px;border: 1px solid #ccc;border-radius: 4px;"
(change)="idselected($event.target.value)">
<option *ngFor="let sub of menus" [value]="sub.menuItemId">{{sub.menuItemDesc}}</option>
</select>
<div class="mac-form-help">{{'CHOOSE_MAIN_MENU_FOR_ACCESS' | translate}}</div>
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="mac-btn mac-btn-outline" (click)="modalAdd = false">
<clr-icon shape="times"></clr-icon>
<span>{{'CANCEL' | translate}}</span>
</button>
<button type="submit" class="mac-btn mac-btn-primary">
<clr-icon shape="check"></clr-icon>
<span>{{'ADD' | translate}}</span>
</button>
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
<button type="submit" class="btn btn-primary">ADD</button>
</div>
</form>
</div>
</clr-modal>
<clr-modal class="mac-modal" [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title mac-modal-title">
<clr-icon shape="edit"></clr-icon> {{'UPDATE_MENU_ACCESS_CONTROL' | translate}}
</h3>
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Update Menu Access Control</h3>
<div class="modal-body" *ngIf="rowSelected.menuItemId">
<div class="mac-form-help">{{'MENU_ID' | translate}}:
<code class="clr-code">{{rowSelected.menuItemId.menuItemId}}</code>
</div>
<h2 class="heading">{{rowSelected.menuItemId.menuItemId}}</h2>
<form (ngSubmit)="onUpdate(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)">
<div class="mac-perms">
<label class="mac-perm">
<input type="checkbox" clrCheckbox name="mvisible" [(ngModel)]="selected ===rowSelected.mvisible" (change)="rowSelected.mvisible === $event.target.checked ? 'y' : 'n'">
<span class="mac-perm__icon">
<clr-icon shape="eye"></clr-icon>
</span>
<span class="mac-perm__label">{{'VIEW' | translate}}</span>
</label>
<label class="mac-perm">
<input type="checkbox" clrCheckbox name="mcreate" [(ngModel)]="selected ===rowSelected.mcreate" (change)="rowSelected.mcreate === $event.target.checked ? 'y' : 'n'">
<span class="mac-perm__icon">
<clr-icon shape="plus"></clr-icon>
</span>
<span class="mac-perm__label">{{'CREATE' | translate}}</span>
</label>
<label class="mac-perm">
<input type="checkbox" clrCheckbox name="medit" [(ngModel)]="selected ===rowSelected.medit" (change)="rowSelected.medit === $event.target.checked ? 'y' : 'n'">
<span class="mac-perm__icon">
<clr-icon shape="edit"></clr-icon>
</span>
<span class="mac-perm__label">{{'EDIT' | translate}}</span>
</label>
<label class="mac-perm">
<input type="checkbox" clrCheckbox name="mdelete" [(ngModel)]="selected ===rowSelected.mdelete" (change)="rowSelected.mdelete === $event.target.checked ? 'y' : 'n'">
<span class="mac-perm__icon">
<clr-icon shape="trash"></clr-icon>
</span>
<span class="mac-perm__label">{{'DELETE' | translate}}</span>
</label>
<label class="mac-perm">
<input type="checkbox" clrCheckbox name="mquery" [(ngModel)]="selected ===rowSelected.mquery" (change)="rowSelected.mquery === $event.target.checked ? 'y' : 'n'">
<span class="mac-perm__icon">
<clr-icon shape="search"></clr-icon>
</span>
<span class="mac-perm__label">{{'QUERY' | translate}}</span>
</label>
<label class="mac-perm">
<input type="checkbox" clrCheckbox name="mexport" [(ngModel)]="selected ===rowSelected.mexport" (change)="rowSelected.mexport === $event.target.checked ? 'y' : 'n'">
<span class="mac-perm__icon">
<clr-icon shape="export"></clr-icon>
</span>
<span class="mac-perm__label">{{'EXPORT' | translate}}</span>
</label>
<div class="clr-row">
<div class="clr-col-md-3 clr-col-sm-12">
<label for="tags">View</label>
<input type="checkbox" clrCheckbox name="mvisible" [(ngModel)]="selected ===rowSelected.mvisible"
(change)="rowSelected.mvisible === $event.target.checked ? 'y' : 'n'">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Create</label>
<input type="checkbox" clrCheckbox name="mcreate" name="mcreate"
[(ngModel)]="selected ===rowSelected.mcreate"
(change)="rowSelected.mcreate === $event.target.checked ? 'y' : 'n'">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="tags">Edit</label>
<input type="checkbox" clrCheckbox name="medit" [(ngModel)]="selected ===rowSelected.medit"
(change)="rowSelected.medit === $event.target.checked ? 'y' : 'n'">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="tags">Delete</label>
<input type="checkbox" clrCheckbox name="mdelete" [(ngModel)]="selected ===rowSelected.mdelete"
(change)="rowSelected.mdelete === $event.target.checked ? 'y' : 'n'">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="tags">Query</label>
<input type="checkbox" clrCheckbox name="mquery" [(ngModel)]="selected ===rowSelected.mquery"
(change)="rowSelected.mquery === $event.target.checked ? 'y' : 'n'">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="tags">Export</label>
<input type="checkbox" clrCheckbox name="mexport" [(ngModel)]="selected ===rowSelected.mexport"
(change)="rowSelected.mexport === $event.target.checked ? 'y' : 'n'">
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="mac-btn mac-btn-outline" (click)="modaledit = false">
<clr-icon shape="times"></clr-icon>
<span>{{'CANCEL' | translate}}</span>
</button>
<button type="submit" class="mac-btn mac-btn-primary">
<clr-icon shape="check"></clr-icon>
<span>{{'UPDATE' | translate}}</span>
</button>
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
<button type="submit" class="btn btn-primary">UPDATE</button>
</div>
</form>
</div>
</clr-modal>
<clr-modal class="mac-modal" [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body">
<div class="mac-delete-header">
<div class="mac-delete-icon">
<clr-icon shape="exclamation-triangle" size="48"></clr-icon>
</div>
<h1 class="mac-delete-title">{{'ARE_YOU_SURE_WANT_TO_DELETE' | translate}}?</h1>
<p class="mac-delete-subtitle">{{'THIS_ACTION_CANNOT_BE_UNDONE' | translate}}</p>
</div>
<div class="mac-delete-details" *ngIf="rowSelected.menuItemId">
<div class="mac-delete-detail-item">
<span class="mac-delete-detail-label">{{'MENU_ITEM_ID' | translate}}:</span>
<span class="mac-delete-detail-value">{{rowSelected.menuItemId.menuItemId}}</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="mac-btn mac-btn-outline" (click)="modaldelete = false">
<clr-icon shape="times"></clr-icon>
<span>{{'CANCEL' | translate}}</span>
</button>
<button type="submit" (click)="delete(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)" class="mac-btn mac-btn-error">
<clr-icon shape="trash"></clr-icon>
<span>{{'DELETE' | translate}}</span>
</button>
</div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<clr-modal class="mac-modal" [(clrModalOpen)]="modaldelete1" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body">
<div class="mac-delete-header">
<div class="mac-delete-icon">
<clr-icon shape="exclamation-triangle" size="48"></clr-icon>
</div>
<h1 class="mac-delete-title">{{'ARE_YOU_SURE_WANT_TO_DELETE_MAIN_MENU_WITH_SUBMENU' | translate}}?</h1>
<p class="mac-delete-subtitle">{{'THIS_ACTION_CANNOT_BE_UNDONE' | translate}}</p>
</div>
<div class="mac-delete-details" *ngIf="rowSelected.menuItemId">
<div class="mac-delete-detail-item">
<span class="mac-delete-detail-label">{{'MENU_ITEM_ID' | translate}}:</span>
<span class="mac-delete-detail-value">{{rowSelected.menuItemId.menuItemId}}</span>
</div>
</div>
<div class="modal-body" *ngIf="rowSelected.menuItemId">
<h1 class="delete">Are You Sure Want to delete?</h1>
<h2 class="heading">{{rowSelected.menuItemId.menuItemId}}</h2>
<div class="modal-footer">
<button type="button" class="mac-btn mac-btn-outline" (click)="modaldelete1 = false">
<clr-icon shape="times"></clr-icon>
<span>{{'CANCEL' | translate}}</span>
</button>
<button type="submit" (click)="delete1(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)" class="mac-btn mac-btn-error">
<clr-icon shape="trash"></clr-icon>
<span>{{'DELETE' | translate}}</span>
</button>
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
<button type="submit" (click)="delete(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)"
class="btn btn-primary">Delete</button>
</div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modaldelete1" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body" *ngIf="rowSelected.menuItemId">
<h1 class="delete">Are You Sure Want to delete Main menu with submenu?</h1>
<h2 class="heading">{{rowSelected.menuItemId.menuItemId}}</h2>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modaldelete1 = false">Cancel</button>
<button type="submit" (click)="delete1(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)"
class="btn btn-primary">Delete</button>
</div>
</div>
</clr-modal>
</div>

View File

@@ -1,160 +1,43 @@
@import '../../../../../styles/_design-tokens.scss';
// Import base styles
input[type=text],
[type=date],
[type=password],
[type=checkbox] {
input[type=text],[type=date],[type=password],[type=checkbox] {
width: 100%;
padding: 15px 20px;
background-color: var(--theme-surface);
// margin: 3px 0;
background-color:rgb(255, 255, 255);
display: inline-block;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-family: var(--theme-font-primary);
color: var(--theme-text);
}
.required-field{
color: red;
font-size: 18px;
input[type=text]:focus,
[type=date]:focus,
[type=password]:focus,
[type=checkbox]:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
.required-field {
color: var(--theme-error, #ef4444);
font-size: 18px;
}
// select{
// width: 100%;
// padding: 5px 5px;
// border: 1px solid #ccc;
// border-radius: 4px;
// }
.td-title {
text-align: center;
width: 150px;
color: white;
color: white;
font-weight: bold;
background-color: var(--theme-primary);
background-color: rgba(63, 122, 231, 0.863);
//color: rgb(24, 13, 13);
}
th {
th{
//background-color:rgb(170, 169, 169);
font-weight: bold;
}
.td-content {
.td-content{
text-align: left;
}
// Modern Button Styles using ThemeService
.mac-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
line-height: 1;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
transition: all 200ms ease-out;
text-decoration: none;
position: relative;
overflow: hidden;
font-family: var(--theme-font-primary);
z-index: 1;
&:focus {
outline: 2px solid var(--theme-primary);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
// Sizes
&.mac-btn-sm {
padding: 8px 16px;
font-size: 13px;
height: 32px;
}
&.mac-btn-md {
padding: 12px 20px;
font-size: 14px;
height: 40px;
}
&.mac-btn-lg {
padding: 16px 24px;
font-size: 16px;
height: 48px;
}
// Variants
&.mac-btn-primary {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-color: var(--theme-primary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: linear-gradient(135deg, var(--theme-primary, #0284c7) 0%, var(--theme-accent, #7c3aed) 100%);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
&:active {
transform: translateY(0);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
}
&.mac-btn-outline {
background: transparent;
color: var(--theme-secondary);
border-color: var(--theme-secondary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: rgba(100, 116, 139, 0.1);
border-color: var(--theme-secondary);
color: var(--theme-secondary);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.mac-btn-error {
background: var(--theme-error, #ef4444);
color: white;
border-color: var(--theme-error, #ef4444);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-error-dark, #dc2626);
border-color: var(--theme-error-dark, #dc2626);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.3), 0 4px 6px -2px rgba(239, 68, 68, 0.1);
}
}
&.mac-btn-ghost {
background: transparent;
color: var(--theme-text-secondary);
border-color: transparent;
&:hover {
background: var(--theme-background);
color: var(--theme-text);
}
}
.delete,.heading{
text-align: center;
color: red;
}
//toggle button
.switch {
position: relative;
@@ -193,585 +76,24 @@ th {
transition: .4s;
}
input:checked+.slider {
background-color: var(--theme-primary);
input:checked + .slider {
background-color: #0072a3;
}
input:focus+.slider {
box-shadow: 0 0 1px var(--theme-primary);
input:focus + .slider {
box-shadow: 0 0 1px #0072a3;
}
input:checked+.slider:before {
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
.slider.round {
.slider.round {
border-radius: 34px;
}
}
.slider.round:before {
.slider.round:before {
border-radius: 50%;
}
// Modern themed enhancements (ThemeService variables)
.mm-breadcrumb {
color: var(--theme-text-secondary);
}
.mac-header-left {
display: flex;
}
.mac-title h4 {
font-weight: 300;
}
.mac-edit-chip {
margin-top: 27px;
position: relative;
}
.mac-chip {
display: inline-flex;
align-items: center;
padding: 6px 12px;
background-color: var(--theme-background);
color: var(--theme-text);
border-radius: var(--theme-border-radius);
font-size: 14px;
font-weight: 500;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.mac-header-right {
text-align: right;
}
.mac-select {
height: 40px;
padding: 8px 12px;
background: var(--theme-surface);
color: var(--theme-text);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
font-family: var(--theme-font-primary);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
}
.w-100 {
width: 100%;
}
.mac-btn-primary {
margin-left: 20px;
box-shadow: var(--theme-shadow);
}
.mac-actions {
text-align: right;
margin-bottom: 24px;
}
.mac-hint {
color: var(--theme-error, #ef4444);
font-size: 14px;
margin-top: 5px;
}
.mac-grid {
background: var(--theme-surface);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
::ng-deep .datagrid {
.datagrid-head {
background: var(--theme-background);
.datagrid-column {
padding: 16px 24px;
font-size: 12px;
font-weight: 600;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e5e7eb;
}
}
.datagrid-row {
transition: background-color 150ms ease-out;
&:hover {
background: var(--theme-background);
}
&:not(:last-child) {
border-bottom: 1px solid #e5e7eb;
}
.datagrid-cell {
padding: 16px 24px;
font-size: 14px;
color: var(--theme-text);
}
}
.datagrid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
padding: 16px 24px;
}
}
}
.mac-grid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
}
.mac-modal .modal-title {
color: var(--theme-text);
}
// Hero and actions
.mac-hero {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px;
margin-bottom: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
font-family: var(--theme-font-primary);
&__content {
display: flex;
align-items: center;
gap: 16px;
}
&__icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
clr-icon {
width: 36px;
height: 36px;
color: white;
}
}
&__title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: white;
font-family: var(--theme-font-secondary);
}
&__subtitle {
font-size: 16px;
margin: 0;
opacity: 0.9;
color: rgba(255, 255, 255, 0.8);
}
&__actions {
display: flex;
align-items: center;
gap: 16px;
}
}
.mac-sep {
width: 1px;
height: 22px;
background: rgba(255, 255, 255, 0.5);
}
.mac-label {
color: white;
font-weight: 500;
}
.mac-toggle__label {
color: white;
font-weight: 500;
}
// Grid columns
.mac-col-title {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--theme-text-secondary);
}
.mac-col--perm .mac-col-title clr-icon {
color: var(--theme-primary);
}
// Toolbar, search and stats
.mac-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 16px;
background: var(--theme-surface);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&__left {
flex: 1;
}
&__right {
display: flex;
gap: 16px;
}
}
.mac-search {
position: relative;
max-width: 400px;
clr-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
z-index: 1;
}
&__input {
width: 100%;
padding: 12px 12px 12px 40px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
font-size: 14px;
transition: all 200ms ease-out;
background: var(--theme-surface);
color: var(--theme-text);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
font-family: var(--theme-font-primary);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
}
}
.mac-stats {
display: flex;
gap: 16px;
}
.mac-stat {
min-width: 110px;
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.04);
border-radius: var(--theme-border-radius);
padding: 16px;
text-align: center;
box-shadow: var(--theme-shadow);
&__value {
font-size: 24px;
font-weight: 700;
color: var(--theme-primary);
margin-bottom: 4px;
}
&__label {
font-size: 12px;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
}
// Enhanced modals
.mac-modal-title clr-icon {
margin-right: 6px;
color: var(--theme-primary);
}
.mac-form-group {
margin-bottom: 20px;
}
.mac-form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--theme-text);
margin-bottom: 8px;
font-family: var(--theme-font-primary);
}
.mac-form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
line-height: 1.5;
color: var(--theme-text);
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
transition: all 200ms ease-out;
margin-bottom: 0;
font-family: var(--theme-font-primary);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 12px center;
background-repeat: no-repeat;
background-size: 16px 12px;
padding-right: 40px;
appearance: none;
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
&:disabled {
background: var(--theme-background);
color: var(--theme-text-secondary);
cursor: not-allowed;
}
}
.mac-form-help {
color: var(--theme-text-secondary);
font-size: 12px;
margin-top: 6px;
font-family: var(--theme-font-primary);
}
.mac-perms {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
margin: 24px 0;
}
.mac-perm {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--theme-border-radius);
box-shadow: var(--theme-shadow);
transition: all 200ms ease-out;
&:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
input[type="checkbox"] {
width: auto;
padding: 0;
}
}
.mac-perm__icon clr-icon {
color: var(--theme-primary);
width: 20px;
height: 20px;
}
.mac-perm__label {
color: var(--theme-text);
font-weight: 500;
font-family: var(--theme-font-primary);
}
// Datagrid action overflow buttons
.mac-action-item {
@extend .mac-btn;
@extend .mac-btn-ghost;
@extend .mac-btn-sm;
width: 100%;
justify-content: flex-start;
margin-bottom: 4px;
text-align: left;
clr-icon {
width: 16px;
height: 16px;
margin-right: 8px;
}
&.mac-btn-error {
color: var(--theme-error, #ef4444);
&:hover {
background: rgba(239, 68, 68, 0.1);
}
}
}
// Delete Modal Styles
.mac-delete-header {
text-align: center;
padding: 24px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.mac-delete-icon {
color: var(--theme-error, #ef4444);
margin-bottom: 16px;
}
.mac-delete-title {
font-size: 24px;
font-weight: 700;
color: var(--theme-text);
margin: 0 0 8px 0;
}
.mac-delete-subtitle {
font-size: 16px;
color: var(--theme-text-secondary);
margin: 0;
}
.mac-delete-details {
padding: 24px;
}
.mac-delete-detail-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:last-child {
border-bottom: none;
}
}
.mac-delete-detail-label {
font-weight: 500;
color: var(--theme-text-secondary);
}
.mac-delete-detail-value {
font-weight: 600;
color: var(--theme-text);
}
.delete,
.heading {
text-align: center;
color: var(--theme-error, #ef4444);
}
// Modal Header
::ng-deep .modal-header {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px 16px 0 0 !important;
padding: 20px 24px !important;
.modal-title {
color: white;
font-weight: 600;
font-size: 20px;
margin: 0;
}
.close {
color: white;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
// Modal Body
::ng-deep .modal-body {
padding: 24px !important;
}
// Modal Footer
::ng-deep .modal-footer {
padding: 20px 24px !important;
background: var(--theme-background);
border-radius: 0 0 16px 16px !important;
border-top: 1px solid rgba(0, 0, 0, 0.05) !important;
}
// Responsive adjustments
@media (max-width: 768px) {
.mac-hero {
flex-direction: column;
gap: 16px;
text-align: center;
&__content {
flex-direction: column;
text-align: center;
}
&__actions {
flex-direction: column;
width: 100%;
}
}
.mac-toolbar {
flex-direction: column;
gap: 16px;
}
.mac-search {
max-width: 100%;
}
.mac-stats {
flex-direction: column;
width: 100%;
}
.mac-perms {
grid-template-columns: 1fr;
}
.mac-actions {
text-align: center;
}
.mac-btn {
width: 100%;
justify-content: center;
}
}

View File

@@ -6,8 +6,6 @@ import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
import { MenumaintanceService } from 'src/app/services/admin/menumaintance.service';
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
import { ThemeService } from 'src/app/services/theme.service';
@Component({
selector: 'app-menuaccesscontrol',
templateUrl: './menuaccesscontrol.component.html',
@@ -38,26 +36,15 @@ export class MenuaccesscontrolComponent implements OnInit {
toggle: boolean = false;
maindata;
showdata;
// UI enhancements (no API changes)
filterText: string = '';
constructor(
private mainservice: UsermaintanceService,
constructor(private mainservice: UsermaintanceService,
private _fb: FormBuilder,
private toastr: ToastrService,
private route: ActivatedRoute,
private usergrpservice: UsergrpmaintainceService,
private menuGroupService: MenuGroupService,
private menuservice: MenumaintanceService,
private themeService: ThemeService
) { }
private menuservice: MenumaintanceService,) { }
ngOnInit(): void {
// Ensure theme variables are applied to the view
this.themeService.currentTheme$.subscribe(() => {
// CSS variables are updated globally; no extra handling needed here
});
this.showdata = this.menuGroupService.getdata();
console.log(this.showdata);
this.mcreate = this.showdata.mcreate;
@@ -74,64 +61,35 @@ export class MenuaccesscontrolComponent implements OnInit {
grpid: this.usergrpid,
gmenuid: this.menuselectid,
});
}
// Stats & filtering helpers for UI only
get totalItems(): number {
const list: any[] = (this.alldata as unknown as any[]) || [];
return list.length;
}
get mainMenuCount(): number {
const list: any[] = (this.alldata as unknown as any[]) || [];
return list.filter(it => it && it.menuId === 0).length;
}
get subMenuCount(): number {
const list: any[] = (this.alldata as unknown as any[]) || [];
return list.filter(it => it && it.menuId !== 0).length;
}
get filteredAllData(): any[] {
const items: any[] = (this.alldata as unknown as any[]) || [];
const text = (this.filterText || '').toLowerCase();
if (!text) { return items; }
return items.filter(m => (
(m?.menuItemDesc || '').toLowerCase().includes(text)
));
}
dropddowngetdata() {
this.usergrpservice.getAll().subscribe((data) => {
console.log(data);
this.givendata = data;
});
}
getdata() {
this.menuservice.getByCurrentUserMenuGroupId1().subscribe(resp => {
this.menus = resp;
console.log('menus: ', this.menus);
})
}
getall() {
this.usergrpservice.getall().subscribe((data) => {
this.secmenuaccessdata = data
console.log(data);
})
}
idofselected(val) {
console.log(val);
this.usergrpid = val;
}
}
idselected(val) {
console.log(val)
this.menuselectid = val;
}
getbyuseriddata() {
this.usergrpservice.getbyusergrpid(this.usergrpid).subscribe((data) => {
this.alldata = data;
@@ -153,12 +111,10 @@ export class MenuaccesscontrolComponent implements OnInit {
}
});
}
modaladd() {
this.modalAdd = true;
this.getdata();
}
onSubmit() {
this.modalAdd = false;
this.entryForm.value.grpid = this.usergrpid;
@@ -182,20 +138,18 @@ export class MenuaccesscontrolComponent implements OnInit {
}
this.ngOnInit();
});
}
}
modalEdit(row) {
this.rowSelected = row;
console.log(this.rowSelected);
this.modaledit = true;
}
modalDelete(row) {
this.rowSelected = row;
console.log(this.rowSelected)
this.modaldelete = true;
}
delete(id, usrgrp) {
this.modaldelete = false;
this.usergrpservice.delete(id, usrgrp).subscribe((data) => {
@@ -212,13 +166,11 @@ export class MenuaccesscontrolComponent implements OnInit {
this.ngOnInit();
});
}
modaldeletemainmenu(row) {
this.rowSelected = row;
console.log(this.rowSelected)
this.modaldelete1 = true;
}
delete1(id, usrgrp) {
this.modaldelete1 = false;
this.usergrpservice.deletemain(id, usrgrp).subscribe((data) => {
@@ -235,7 +187,6 @@ export class MenuaccesscontrolComponent implements OnInit {
this.ngOnInit();
});
}
onUpdate(id: any, usrgrp: any) {
this.modaledit = false;
console.log(id, usrgrp);
@@ -253,7 +204,6 @@ export class MenuaccesscontrolComponent implements OnInit {
this.ngOnInit();
});
}
onChecked(value) {
if (value == "y") {
this.selected = "y"
@@ -263,13 +213,11 @@ export class MenuaccesscontrolComponent implements OnInit {
this.selected = "n"// make a call for unchecked
}
}
changedelete(val) {
console.log(val);
val = this.colvalue = val;
console.log(val);
}
data: {};
Sync(id: any, row) {
this.rowSelected = row;
@@ -291,7 +239,6 @@ export class MenuaccesscontrolComponent implements OnInit {
this.ngOnInit();
})
}
toggleCheckbox() {
this.toggle = !this.toggle;
//this.dataService.setDivToggler(this.toggler);

View File

@@ -1,39 +1,11 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon>{{ 'SECURITY' | translate }}</a></li>
<li><a href="javascript://">{{ 'MENU_MAINTENANCE' | translate }}</a></li>
</ol>
<div class="mm-container">
<section class="mm-hero">
<div class="mm-hero__content">
<div class="mm-hero__icon">
<clr-icon shape="organization"></clr-icon>
</div>
<div>
<h2 class="mm-hero__title">{{ 'MENU_MAINTENANCE' | translate }}</h2>
<p class="mm-hero__subtitle">{{ 'MAIN_MENU' | translate }}</p>
</div>
</div>
<div class="mm-hero__actions">
<button class="btn btn-outline mm-btn-outline" (click)="onExport()">
<clr-icon shape="export"></clr-icon>
<span class="mm-btn-text">{{ 'EXPORT_XLSX' | translate }}</span>
</button>
<button class="btn btn-primary mm-btn-primary" (click)="csvImport()">
<clr-icon shape="import"></clr-icon>
<span class="mm-btn-text">{{ 'IMPORT' | translate }}</span>
</button>
<button id="add" class="btn btn-primary mm-btn-primary" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon>
<span class="mm-btn-text">{{ 'ADD' | translate }}</span>
</button>
<button class="btn mm-btn-secondary" (click)="downloadFiles()">
<clr-icon shape="download"></clr-icon>
<span class="mm-btn-text">{{ 'DOWNLOAD_TEMPLATE' | translate }}</span>
</button>
</div>
</section>
<!-- <div class="clr-row">
<div class="clr-col-12" style="text-align: right;">
<button id="add" *ngIf="mcreate == 'true'" class="btn btn-primary" (click)="goToAdd()">
@@ -41,44 +13,29 @@
</button>
</div>
</div> -->
<div class="mm-stats">
<div class="mm-stat">
<div class="mm-stat__value">{{ totalMenus }}</div>
<div class="mm-stat__label">{{ 'TOTAL_MENUS' | translate }}</div>
</div>
<div class="mm-stat">
<div class="mm-stat__value">{{ enabledMenusCount }}</div>
<div class="mm-stat__label">{{ 'ENABLED' | translate }}</div>
</div>
<div class="mm-stat">
<div class="mm-stat__value">{{ disabledMenusCount }}</div>
<div class="mm-stat__label">{{ 'DISABLED' | translate }}</div>
</div>
<div class="clr-row" style="margin-top: 15px;">
<div class="clr-col">
<h3 style="font-weight: 300;display: inline;">{{ 'MENU_MAINTENANCE' | translate }} </h3>
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">{{ 'MAIN_MENU' | translate }}</span><br>
</div>
<div class="mm-toolbar">
<div class="mm-toolbar__left">
<div class="mm-search">
<clr-icon shape="search"></clr-icon>
<input class="mm-search__input" type="text" [placeholder]="'SEARCH' | translate" [(ngModel)]="filterText" />
<div class="clr-col" style="text-align: right;">
<div class="btn-group">
<button class="btn text-dark" (click)="downloadFiles()"><b>{{ 'DOWNLOAD_TEMPLATE' | translate }}</b></button>
</div>
</div>
<div class="mm-toolbar__right">
<select class="mm-select" [(ngModel)]="statusFilter">
<option value="All">{{ 'ALL' | translate }}</option>
<option value="Enable">{{ 'ENABLED' | translate }}</option>
<option value="Disable">{{ 'DISABLED' | translate }}</option>
</select>
<div class="mm-view-toggle">
<button class="btn btn-sm" [class.btn-primary]="viewMode==='cards'" (click)="setViewMode('cards')">
<clr-icon shape="view-cards"></clr-icon>
<button class="btn btn-primary" (click)="csvImport()">{{ 'IMPORT' | translate }}</button>
<button class="btn btn-outline" (click)="onExport()">
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
</button>
<button class="btn btn-sm" [class.btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
<clr-icon shape="table"></clr-icon>
<button id="add" class="btn btn-primary" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
</button>
</div>
</div>
</div>
</div>
<!-- <table class="table">
<thead>
<tr>
@@ -187,81 +144,62 @@
</tbody>
</table> -->
<div class="mm-skeleton-list" *ngIf="loading">
<div class="mm-skeleton-item" *ngFor="let i of [1,2,3,4,5,6]">
<div class="mm-skel mm-skel--icon"></div>
<div class="mm-skel mm-skel--title"></div>
<div class="mm-skel mm-skel--meta"></div>
<div class="mm-skel mm-skel--chip"></div>
<div class="mm-skel mm-skel--link"></div>
<div class="mm-skel mm-skel--badge"></div>
<div class="mm-skel mm-skel--actions"></div>
</div>
</div>
<div class="mm-card" *ngIf="viewMode==='table'">
<clr-datagrid class="mm-grid mm-grid--modern" [clrDgLoading]="loading" [(clrDgSelected)]="selected">
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{'LOADING'|translate}} ...
</clr-spinner></ng-template>
<div class="mm-empty" *ngIf="error;else loadingSpinner">
<clr-icon shape="warning-standard"></clr-icon>
<div class="mm-empty__title">{{ 'NO_DATA' | translate }}</div>
<div class="mm-empty__subtitle">{{ error }}</div>
</div>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="''" class="mm-col mm-col--icon">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected">
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{'LOADING'|translate}} ... </clr-spinner></ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
<clr-dg-column [clrDgField]="''" style="max-width: 40px;">
<ng-container *clrDgHideableColumn="{hidden: false}"></ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--no">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="hashtag"></clr-icon> {{'NO' | translate}}</span>
{{'NO' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--name">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="tag"></clr-icon> {{'MENU_ITEM_NAME' | translate}}</span>
{{'MENU_ITEM_NAME' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--id">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="id-badge"></clr-icon> {{'ID' | translate}}</span>
{{'ID' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--seq">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="number-list"></clr-icon> {{'SEQUENCE' | translate}}</span>
{{'SEQUENCE' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--module">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="application"></clr-icon> {{'MODULE_NAME' | translate}}</span>
{{'MODULE_NAME' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--link">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="link"></clr-icon> {{'MENU_ACTION_LINK' | translate}}</span>
{{'MENU_ACTION_LINK' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--status">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<span class="mm-col-title"><clr-icon shape="shield-check"></clr-icon> {{'STATUS' | translate}}</span>
{{'STATUS' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--submenu">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'SUB_MENU' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column class="mm-col mm-col--action">
<clr-dg-column>
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-icon shape="bars"></clr-icon>
{{'ACTION' | translate}}
@@ -269,49 +207,22 @@
</clr-dg-column>
<clr-dg-row class="mm-row" *clrDgItems="let user of filteredMenus;let i = index" [clrDgItem]="user">
<clr-dg-cell class="mm-cell mm-cell--icon">
<span class="mm-icon-action"><clr-icon shape="edit" (click)="goToEdit(user)"
class="is-error mm-icon-edit"></clr-icon></span>
<clr-dg-row *clrDgItems="let user of menus;let i = index" [clrDgItem]="user">
<clr-dg-cell style="max-width: 40px;">
<span style="cursor: pointer;"><clr-icon shape="edit" (click)="goToEdit(user)" class="red is-error" style="color:red;"></clr-icon></span>
</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--no">{{i+1}}</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--name">
<div class="mm-item">
<div class="mm-item__avatar"><clr-icon shape="bookmark"></clr-icon></div>
<div class="mm-item__text">
<div class="mm-item__title">{{user.menuItemDesc}}</div>
<div class="mm-item__meta">ID: {{user.menuId}} • Seq: {{user.itemSeq}}</div>
</div>
</div>
</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--id">{{user.menuId}}</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--seq">{{user.itemSeq}}</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--module">
<div class="mm-chip mm-chip--module">
<clr-icon shape="application"></clr-icon>
<span>{{user.moduleName}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--link">
<div class="mm-link">
<clr-icon shape="link"></clr-icon>
<span>{{user.main_menu_action_name}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--status">
<span class="mm-badge" [class.mm-badge--success]="user.status==='Enable'"
[class.mm-badge--muted]="user.status!=='Enable'">{{user.status}}</span>
</clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--submenu"><clr-icon shape="bullet-list" (click)="submenu(user.menuItemId)"
class="mm-icon-link"></clr-icon> </clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--action">
<div class="mm-actions-inline">
<button class="btn btn-sm btn-outline" (click)="goToEdit(user)"><clr-icon shape="edit"></clr-icon></button>
<button class="btn btn-sm btn-outline" (click)="onDelete(user)"><clr-icon shape="trash"></clr-icon></button>
</div>
<clr-dg-cell >{{i+1}}</clr-dg-cell>
<clr-dg-cell>{{user.menuItemDesc}}</clr-dg-cell>
<clr-dg-cell >{{user.menuId}}</clr-dg-cell>
<clr-dg-cell >{{user.itemSeq}}</clr-dg-cell>
<clr-dg-cell > {{user.moduleName}} </clr-dg-cell>
<clr-dg-cell > {{user.main_menu_action_name}}</clr-dg-cell>
<clr-dg-cell >{{user.status}} </clr-dg-cell>
<clr-dg-cell ><clr-icon shape="bullet-list" (click)="submenu(user.menuItemId)" style="color: rgb(108, 108, 194);"></clr-icon> </clr-dg-cell>
<clr-dg-cell>
<span style="cursor: pointer;padding: 10px; "><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error" style="color: red;"></clr-icon></span>
<clr-signpost>
<span class="mm-icon-action" clrSignpostTrigger><clr-icon shape="help"
class="mm-icon-help"></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>
<h5 style="margin-top: 0">{{'WHO_COLUMN'|translate}} </h5>
<div>{{'ACCOUNT_ID'|translate}} <code class="clr-code">{{user.accountId}}</code></div>
@@ -327,16 +238,17 @@
<button class="action-item" *ngIf="mdelete == 'true'" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button>
</clr-dg-action-overflow> -->
<clr-dg-row-detail *clrIfExpanded>
<div class="mm-detail">
<div class="mm-detail__row"><span>Menu Name</span><strong>{{user.menuItemDesc}}</strong></div>
<div class="mm-detail__row"><span>Action Link</span><strong>{{user.main_menu_action_name}}</strong></div>
<div class="mm-detail__row"><span>Module</span><strong>{{user.moduleName}}</strong></div>
</div>
<clr-dg-row-detail *clrIfExpanded >
<table class="table">
<tr>
<td class="td-title">Menu Name: </td>
<td class="td-content">{{user.menuItemDesc}}</td>
</tr>
</table>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer class="mm-grid-footer">
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
@@ -344,37 +256,8 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
<div *ngIf="viewMode==='cards'">
<div class="mm-cards">
<div class="mm-card-item" *ngFor="let user of filteredMenus; let i = index">
<div class="mm-card-item__header">
<!-- <div class="mm-card-item__icon"><clr-icon [shape]="getIconShape(user)"></clr-icon></div> -->
<div class="mm-card-item__title">{{user.menuItemDesc}}</div>
<div class="mm-card-item__badge" [class.mm-badge--success]="user.status==='Enable'"
[class.mm-badge--muted]="user.status!=='Enable'">{{user.status}}</div>
</div>
<div class="mm-card-item__body">
<div class="mm-kv"><span>ID</span><strong>{{user.menuId}}</strong></div>
<div class="mm-kv"><span>Sequence</span><strong>{{user.itemSeq}}</strong></div>
<div class="mm-kv"><span>Module</span><strong>{{user.moduleName}}</strong></div>
<div class="mm-kv mm-link"><clr-icon shape="link"></clr-icon><strong>{{user.main_menu_action_name}}</strong>
</div>
</div>
<div class="mm-card-item__footer">
<button class="btn btn-sm btn-outline" (click)="goToEdit(user)"><clr-icon shape="edit"></clr-icon>
Edit</button>
<button class="btn btn-sm btn-outline" (click)="onDelete(user)"><clr-icon shape="trash"></clr-icon>
Delete</button>
<button class="btn btn-sm" (click)="submenu(user.menuItemId)"><clr-icon shape="bullet-list"></clr-icon>
Submenu</button>
</div>
</div>
</div>
</div>
<clr-modal class="mm-modal" [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Add Menu Maintenance</h3>
<div class="modal-body">
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
@@ -391,28 +274,24 @@
<label for="name">Menu Item Name<span class="required-field">*</span></label>
<input type="text" class="clr-input" formControlName="menuItemDesc">
<div *ngIf="submitted && entryForm.controls.menuItemDesc.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.menuItemDesc.errors.required" class="error_mess">*This field
is Required</div>
<div *ngIf="submitted && entryForm.controls.menuItemDesc.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="clr-col-sm-12">
<label for="tags">Sequence</label>
<input type="text" class="clr-input" formControlName="itemSeq">
<div *ngIf="submitted && entryForm.controls.itemSeq.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.required" class="error_mess">*This field is
Required</div>
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.required" class="error_mess">*This field is Required</div>
</div>
<div *ngIf="submitted && entryForm.controls.itemSeq.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.pattern" class="error_mess">*Required Only
Number</div>
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.pattern" class="error_mess">*Required Only Number</div>
</div>
</div>
<div class="clr-col-sm-12">
<label for="tags">Module Name</label>
<input type="text" class="clr-input" formControlName="moduleName">
<div *ngIf="submitted && entryForm.controls.moduleName.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.moduleName.errors.required" class="error_mess">*This field is
Required</div>
<div *ngIf="submitted && entryForm.controls.moduleName.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="clr-col-sm-12">
@@ -422,16 +301,14 @@
<option value="Disable">Disable</option>
</select>
<div *ngIf="submitted && entryForm.controls.status.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.status.errors.required" class="error_mess">*This field is
Required</div>
<div *ngIf="submitted && entryForm.controls.status.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="clr-col-sm-12">
<label for="tags">Menu Action Link</label>
<input type="text" class="clr-input" formControlName="main_menu_action_name">
<div *ngIf="submitted && entryForm.controls.main_menu_action_name.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.main_menu_action_name.errors.required" class="error_mess">
*This field is Required</div>
<div *ngIf="submitted && entryForm.controls.main_menu_action_name.errors.required" class="error_mess">*This field is Required</div>
</div>
<!-- <select NAME="menu_id" >
<option SELECTED >Home Menu
@@ -476,35 +353,34 @@
<label for="tags">Menu Icon Name</label>
<input type="text" class="clr-input" formControlName="main_menu_icon_name">
<div *ngIf="submitted && entryForm.controls.main_menu_icon_name.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.main_menu_icon_name.errors.required" class="error_mess">*This
field is Required</div>
<div *ngIf="submitted && entryForm.controls.main_menu_icon_name.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
<button type="submit" class="btn btn-primary">ADD</button>
<button type="submit" class="btn btn-primary" >ADD</button>
</div>
</form>
</div>
</clr-modal>
<clr-modal class="mm-modal" [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body" *ngIf="rowSelected.menuItemId">
<h1 class="delete">Are You Sure Want to delete?</h1>
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
<button type="submit" (click)="delete(rowSelected.menuItemId)" class="btn btn-primary">Delete</button>
<button type="submit" (click)="delete(rowSelected.menuItemId)" class="btn btn-primary" >Delete</button>
</div>
</div>
</clr-modal>
<clr-modal class="mm-modal" [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Update Menu Maintenance</h3>
<div class="modal-body" *ngIf="rowSelected.menuItemId">
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
@@ -536,19 +412,17 @@
</div>
<div class="clr-col-sm-12">
<label for="tags">Menu Action Link</label>
<input type="text" class="clr-input" name="main_menu_action_name"
[(ngModel)]="rowSelected.main_menu_action_name">
<input type="text" class="clr-input" name="main_menu_action_name" [(ngModel)]="rowSelected.main_menu_action_name">
</div>
<div class="clr-col-sm-12">
<label for="tags">Menu Icon Name </label>
<input type="text" class="clr-input" name="main_menu_icon_name"
[(ngModel)]="rowSelected.main_menu_icon_name">
<input type="text" class="clr-input" name="main_menu_icon_name" [(ngModel)]="rowSelected.main_menu_icon_name">
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
<button type="submit" class="btn btn-primary">Update</button>
<button type="submit" class="btn btn-primary" >Update</button>
</div>
</form>
</div>
@@ -556,14 +430,12 @@
</clr-modal>
<clr-modal class="mm-modal" [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
<clr-modal [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
<h3 class="modal-title">Import File</h3>
<div class="modal-body">
<input type="file" name="file" class="file" (change)="selectFile($event)"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
<input type="file" name="file" class="file" (change)="selectFile($event)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
</div>
</clr-modal>
</div>

View File

@@ -60,591 +60,3 @@ select{
border-radius: 4px;
}
/* Modern theme-aware enhancements */
.mm-container {
background: var(--theme-surface);
border-radius: var(--theme-border-radius);
box-shadow: var(--theme-shadow);
padding: 1rem;
}
.mm-hero {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--theme-glass-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--theme-glass-border);
border-radius: calc(var(--theme-border-radius) + 4px);
padding: 1rem 1.25rem;
margin-bottom: 0.75rem;
box-shadow: var(--theme-glass-shadow);
}
.mm-hero__content {
display: flex;
align-items: center;
gap: 0.75rem;
}
.mm-hero__icon clr-icon {
width: 28px;
height: 28px;
color: var(--theme-primary);
filter: drop-shadow(0 4px 10px rgba(0,0,0,0.15));
}
.mm-hero__title {
font-size: 1.25rem;
line-height: 1.2;
margin: 0;
}
.mm-hero__subtitle {
margin: 0;
font-size: 0.875rem;
color: var(--theme-text-secondary);
}
.mm-hero__actions .btn + .btn {
margin-left: 0.5rem;
}
.mm-breadcrumb {
margin-bottom: 0.75rem;
}
.mm-header-row {
align-items: center;
margin-top: 0.75rem;
}
.mm-title-wrap {
display: inline-flex;
align-items: center;
gap: 0.75rem;
}
.mm-title {
display: inline-block;
font-weight: 600;
color: var(--theme-text);
}
.mm-subtitle {
display: inline-block;
}
.mm-actions {
text-align: right;
}
.mm-btn-group {
margin-right: 0.5rem;
}
.mm-btn-primary {
border-radius: var(--theme-border-radius);
background: var(--theme-primary);
color: #fff;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: transform 120ms var(--ease-out, cubic-bezier(0, 0, 0.2, 1)), box-shadow 150ms ease;
}
.mm-btn-primary:hover { box-shadow: 0 4px 10px rgba(0,0,0,0.12); transform: translateY(-1px); }
.mm-btn-primary:active { transform: translateY(0); }
.mm-btn-outline {
border-radius: var(--theme-border-radius);
border: 1px solid var(--theme-primary);
color: var(--theme-primary);
background: transparent;
}
.mm-btn-outline:hover { background: var(--theme-primary-100); }
.mm-btn-secondary {
border-radius: var(--theme-border-radius);
background: var(--theme-secondary);
color: #fff;
}
.mm-btn-text {
margin-left: 0.35rem;
}
.mm-grid {
background: var(--theme-surface);
border-radius: var(--theme-border-radius);
box-shadow: var(--theme-shadow);
}
.mm-grid--modern .datagrid-header {
background: linear-gradient(135deg, var(--theme-primary-50), transparent);
}
.mm-col-title {
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 600;
}
.mm-col--icon { width: 44px; }
.mm-col--no { width: 70px; }
.mm-col--name { min-width: 220px; }
.mm-col--id { width: 120px; }
.mm-col--seq { width: 120px; }
.mm-col--module { min-width: 160px; }
.mm-col--link { min-width: 220px; }
.mm-col--status { width: 140px; }
.mm-col--submenu { width: 100px; }
.mm-col--action { width: 140px; }
.mm-card {
background: var(--theme-glass-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--theme-glass-border);
border-radius: var(--theme-border-radius);
padding: 0.25rem;
}
.mm-grid-footer {
border-top: 1px solid rgba(0,0,0,0.06);
}
.mm-col--icon,
.mm-cell--icon {
max-width: 40px;
}
.mm-row:hover {
background: var(--theme-primary-50);
}
.mm-icon-action {
cursor: pointer;
padding: 6px;
border-radius: 6px;
transition: background 150ms ease-out, transform 150ms ease-out;
}
.mm-icon-action:hover {
background: var(--theme-primary-100);
transform: translateY(-1px);
}
.mm-icon-edit {
color: var(--theme-accent);
}
.mm-icon-link {
color: var(--theme-secondary);
}
.mm-icon-help {
color: var(--theme-primary);
}
.mm-icon-delete {
color: #ef4444;
}
.mm-danger:hover {
background: rgba(239, 68, 68, 0.12);
}
.mm-modal .modal-body {
background: var(--theme-surface);
}
.mm-modal .modal-title {
font-weight: 600;
}
/* Modernize Add/Edit modals */
.mm-modal .modal-body form {
display: block;
}
.mm-modal .modal-body .clr-row {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 16px;
}
@media (max-width: 640px) {
.mm-modal .modal-body .clr-row { grid-template-columns: 1fr; }
}
.mm-modal label {
display: inline-block;
margin-bottom: 6px;
font-weight: 600;
color: var(--theme-text);
}
.mm-modal input.clr-input,
.mm-modal select {
width: 100%;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid rgba(0,0,0,0.12);
background: var(--theme-background);
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.mm-modal input.clr-input:focus,
.mm-modal select:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px var(--theme-primary-100);
}
.mm-modal .modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.mm-modal .modal-footer .btn.btn-primary {
background: var(--theme-primary);
color: #fff;
border-radius: 10px;
}
.mm-modal .modal-footer .btn.btn-outline {
border-radius: 10px;
border: 1px solid var(--theme-secondary);
color: var(--theme-secondary);
}
.mm-modal .error_mess { color: #ef4444; font-size: 12px; margin-top: 4px; }
.mm-item {
display: flex;
align-items: center;
gap: 0.75rem;
}
.mm-item__avatar clr-icon {
width: 18px;
height: 18px;
color: var(--theme-accent);
}
.mm-item__title {
font-weight: 600;
}
.mm-item__meta {
font-size: 0.75rem;
color: var(--theme-text-secondary);
}
.mm-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid rgba(0,0,0,0.06);
background: var(--theme-primary-50);
}
.mm-link {
display: inline-flex;
align-items: center;
gap: 6px;
}
.mm-badge {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
background: var(--theme-secondary);
color: white;
font-size: 0.75rem;
}
.mm-badge--success {
background: var(--theme-primary);
}
.mm-badge--muted {
background: var(--theme-primary-200);
color: var(--theme-text);
}
.mm-actions-inline .btn {
margin-right: 6px;
}
.mm-detail {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
padding: 8px 0;
}
.mm-detail__row {
display: flex;
justify-content: space-between;
}
/* Skeleton loader */
.mm-skeleton-list {
display: grid;
gap: 10px;
margin: 8px 0 12px;
}
.mm-skeleton-item {
display: grid;
grid-template-columns: 44px 1.2fr 0.8fr 0.6fr 1fr 120px 140px;
align-items: center;
gap: 10px;
padding: 8px;
background: var(--theme-surface);
border-radius: var(--theme-border-radius);
border: 1px solid rgba(0,0,0,0.05);
}
.mm-skel {
height: 12px;
border-radius: 6px;
background: linear-gradient(
90deg,
rgba(0,0,0,0.05) 25%,
rgba(0,0,0,0.08) 37%,
rgba(0,0,0,0.05) 63%
);
background-size: 400% 100%;
animation: mm-shimmer 1.2s ease-in-out infinite;
}
.mm-skel--icon { width: 24px; height: 24px; border-radius: 50%; }
.mm-skel--title { width: 60%; height: 14px; }
.mm-skel--meta { width: 40%; }
.mm-skel--chip { width: 80px; }
.mm-skel--link { width: 60%; }
.mm-skel--badge { width: 70px; }
.mm-skel--actions { width: 60px; }
@keyframes mm-shimmer {
0% { background-position: 100% 0; }
100% { background-position: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
.mm-skel { animation: none; }
}
/* Row mount animation */
.mm-row { transition: transform 220ms var(--ease-out, cubic-bezier(0, 0, 0.2, 1)), background 200ms ease; }
.mm-row.ng-enter { transform: translateY(8px); opacity: 0.001; }
.mm-row.ng-enter-active { transform: translateY(0); opacity: 1; }
/* Responsive layouts */
@media (max-width: 1024px) {
.mm-col--id, .mm-col--seq, .mm-col--submenu { display: none; }
}
@media (max-width: 768px) {
.mm-col--link { display: none; }
.mm-item__meta { display: none; }
.mm-actions-inline .btn { padding: 0 6px; }
}
@media (max-width: 640px) {
.mm-col--action { width: 100px; }
.mm-col--status { width: 110px; }
.mm-hero { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
.mm-hero__actions { width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 6px; }
}
/* Sticky toolbar */
.mm-toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
padding: 8px 10px;
margin-bottom: 8px;
background: var(--theme-surface);
border: 1px solid rgba(0,0,0,0.05);
border-radius: var(--theme-border-radius);
box-shadow: 0 1px 0 rgba(0,0,0,0.03);
}
.mm-search {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid rgba(0,0,0,0.08);
padding: 6px 8px;
border-radius: 999px;
background: var(--theme-background);
}
.mm-search__input {
border: none;
outline: none;
background: transparent;
min-width: 220px;
}
.mm-select {
border-radius: 999px;
padding: 6px 10px;
border: 1px solid rgba(0,0,0,0.08);
background: var(--theme-background);
}
@media (max-width: 640px) {
.mm-search__input { min-width: 140px; }
}
.mm-view-toggle .btn { margin-left: 6px; border-radius: 999px; }
/* Card view */
.mm-cards {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 1024px) { .mm-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .mm-cards { grid-template-columns: 1fr; } }
.mm-card-item {
background: var(--theme-surface);
border: 1px solid rgba(0,0,0,0.06);
border-radius: var(--theme-border-radius);
box-shadow: var(--theme-shadow);
overflow: hidden;
display: flex;
flex-direction: column;
}
.mm-card-item__header {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: linear-gradient(135deg, var(--theme-primary-50), transparent);
min-height: 48px;
}
.mm-card-item__icon clr-icon { color: var(--theme-primary); width: 18px; height: 18px; }
.mm-card-item__title { font-weight: 700; flex: 1 1 auto; }
.mm-card-item__badge { margin-left: auto; }
/* Prevent overlaps and long text issues */
.mm-ellipsis {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mm-card-item__title {
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width: 640px) {
.mm-card-item__title { max-width: 54%; }
}
.mm-card-item__body {
padding: 10px 12px;
display: grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap: 8px;
}
@media (max-width: 640px) { .mm-card-item__body { grid-template-columns: 1fr; } }
.mm-kv { display: flex; justify-content: space-between; }
.mm-kv span { color: var(--theme-text-secondary); }
.mm-card-item__footer {
padding: 10px 12px;
display: flex;
gap: 8px;
border-top: 1px solid rgba(0,0,0,0.06);
}
/* Theme the card footer buttons */
.mm-card-item__footer .btn {
border-radius: 10px;
}
.mm-card-item__footer .btn.btn-outline {
border: 1px solid var(--theme-secondary);
color: var(--theme-secondary);
background: transparent;
}
.mm-card-item__footer .btn.btn-outline:hover { background: rgba(0,0,0,0.04); }
.mm-card-item__footer .btn:not(.btn-outline) {
background: var(--theme-primary);
color: #fff;
}
.mm-card-item__footer .btn:not(.btn-outline):hover {
filter: brightness(0.98);
}
/* View toggle buttons */
.mm-view-toggle .btn.btn-primary { background: var(--theme-primary); color: #fff; }
.mm-view-toggle .btn:not(.btn-primary) { background: var(--theme-primary-100); color: var(--theme-text); }
.mm-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.75rem;
margin: 0.75rem 0 1rem;
}
.mm-stat {
background: var(--theme-surface);
border-radius: var(--theme-border-radius);
padding: 0.75rem 1rem;
border: 1px solid rgba(0,0,0,0.05);
}
.mm-stat__value {
font-size: 1.25rem;
font-weight: 700;
color: var(--theme-primary);
}
.mm-stat__label {
font-size: 0.75rem;
color: var(--theme-text-secondary);
}
.mm-empty {
text-align: center;
padding: 1.5rem 0.5rem;
color: var(--theme-text-secondary);
}
.mm-empty clr-icon {
width: 32px;
height: 32px;
color: var(--theme-secondary);
margin-bottom: 0.25rem;
}
.mm-empty__title {
font-weight: 600;
}

View File

@@ -9,7 +9,6 @@ import { CsvService } from 'src/app/services/csv.service';
import { ExcelService } from 'src/app/services/excel.service';
import * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core';
import { ThemeService } from 'src/app/services/theme.service';
@Component({
selector: 'app-menumaintance',
templateUrl: './menumaintance.component.html',
@@ -32,10 +31,6 @@ export class MenumaintanceComponent implements OnInit {
showdata;
error;
submitted = false;
// Quick filter UI state (no API/logic changes)
filterText = '';
statusFilter: 'All' | 'Enable' | 'Disable' | '' = 'All';
viewMode: 'cards' | 'table' = 'cards';
constructor(private menuservice: MenumaintanceService,
private toastr: ToastrService,
private excel: ExcelService,
@@ -44,14 +39,9 @@ export class MenumaintanceComponent implements OnInit {
private menuGroupService: MenuGroupService,
private csvService: CsvService,
private translate: TranslateService,
private router: Router,
private themeService: ThemeService) { }
private router: Router,) { }
ngOnInit(): void {
// Ensure theme variables are applied; subscription keeps this view reactive to theme changes
this.themeService.currentTheme$.subscribe(() => {
// Theme applied globally via CSS variables; no additional handling needed here
});
this.showdata = this.menuGroupService.getdata();
console.log(this.showdata);
this.mcreate = this.showdata.mcreate;
@@ -79,71 +69,6 @@ export class MenumaintanceComponent implements OnInit {
this.getdata();
}
// Stats for UI (template-safe, no inline arrow functions)
get totalMenus(): number {
return this.menus ? this.menus.length : 0;
}
get enabledMenusCount(): number {
const list: any[] = (this.menus as unknown as any[]) || [];
return list.filter(menu => menu && menu.status === 'Enable').length;
}
get disabledMenusCount(): number {
const list: any[] = (this.menus as unknown as any[]) || [];
return list.filter(menu => menu && menu.status === 'Disable').length;
}
// Filtered list for view
get filteredMenus(): Rn_Main_Menu[] {
const items: any[] = (this.menus as unknown as any[]) || [];
const text = (this.filterText || '').toLowerCase();
const status = this.statusFilter;
return items.filter(m => {
const matchText = !text || (
(m.menuItemDesc || '').toLowerCase().includes(text) ||
(m.moduleName || '').toLowerCase().includes(text) ||
(m.main_menu_action_name || '').toLowerCase().includes(text)
);
const matchStatus = !status || status === 'All' || m.status === status;
return matchText && matchStatus;
});
}
setViewMode(mode: 'cards' | 'table') {
this.viewMode = mode;
}
// Resolve a safe Clarity icon shape for a menu item
getIconShape(menu: any): string {
const raw = (menu?.main_menu_icon_name ?? menu?.mainMenuIconName ?? '').toString().trim();
const name = raw.toLowerCase();
const invalid = !name || name === 'undefined' || name === 'null' || name === '-' || name === 'na' || name === 'n/a';
if (invalid) {
return 'file'; // universal fallback icon
}
// Optional alias normalization
const aliasMap: Record<string, string> = {
'home': 'home',
'dashboard': 'dashboard',
'menu': 'list',
'list': 'list',
'link': 'link',
'application': 'application',
'applications': 'applications',
'module': 'application',
'settings': 'cog',
'config': 'cog',
'user': 'user',
'users': 'users',
'folder': 'folder',
'file': 'file',
'tag': 'tag',
'bookmark': 'bookmark'
};
return aliasMap[name] ?? name;
}
switchLanguage(language: string) {
this.translate.use(language);
}

View File

@@ -1,5 +1,5 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon>Security</a></li>
<li><a href="javascript://">Menu Maintenance</a></li>
@@ -9,50 +9,22 @@
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Sub Menu</span><br> -->
<section class="mm-hero">
<div class="mm-hero__content">
<div class="mm-hero__icon"><clr-icon shape="organization"></clr-icon></div>
<div>
<h2 class="mm-hero__title">Sub-Menu Maintenance</h2>
<p class="mm-hero__subtitle">Sub Menu</p>
</div>
</div>
<div class="mm-hero__actions">
<button id="add" class="btn btn-primary mm-btn-primary" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon>
<span class="mm-btn-text">Add</span>
</button>
</div>
</section>
<div class="clr-row" style="margin-top: 15px;">
<div class="clr-col-8">
<h3 style="font-weight: 300;display: inline;">Sub-Menu Maintenance </h3>
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Sub Menu</span><br>
<div class="mm-toolbar">
<div class="mm-toolbar__left">
<div class="mm-search">
<clr-icon shape="search"></clr-icon>
<input class="mm-search__input" type="text" placeholder="Search" [(ngModel)]="filterText" />
</div>
</div>
<div class="mm-toolbar__right">
<select class="mm-select" [(ngModel)]="statusFilter">
<option value="All">All</option>
<option value="Enable">Enabled</option>
<option value="Disable">Disabled</option>
</select>
<div class="mm-view-toggle">
<button class="btn btn-sm" [class.btn-primary]="viewMode==='cards'" (click)="setViewMode('cards')">
<clr-icon shape="view-cards"></clr-icon>
</div>
<div class="clr-col-4" style="text-align: right;">
<button id="add" class="btn btn-primary" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon>ADD New
</button>
<button class="btn btn-sm" [class.btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
<clr-icon shape="table"></clr-icon>
</button>
</div>
</div>
</div>
<div class="mm-card" *ngIf="viewMode==='table'">
<clr-datagrid class="mm-grid mm-grid--modern" [clrDgLoading]="loading">
<clr-datagrid [clrDgLoading]="loading" >
<clr-dg-placeholder><clr-spinner [clrMedium]="true">Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column > <ng-container *clrDgHideableColumn="{hidden: false}">
@@ -77,29 +49,19 @@
Status
</ng-container></clr-dg-column>
<clr-dg-row class="mm-row" *clrDgItems="let user of filteredSubMenus;let i = index" [clrDgItem]="user">
<clr-dg-row *clrDgItems="let user of sub;let i = index" [clrDgItem]="user">
<clr-dg-cell >{{i+1}}</clr-dg-cell>
<clr-dg-cell>
<div class="mm-item">
<!-- <div class="mm-item__avatar"><clr-icon [shape]="getIconShape(user)"></clr-icon></div> -->
<div class="mm-item__text">
<div class="mm-item__title">{{user.menuItemDesc}}</div>
<div class="mm-item__meta">ID: {{ user.menuId || '—' }} | Seq: {{ (user.itemSeq === 0 || user.itemSeq) ? user.itemSeq : '—' }}</div>
</div>
</div>
</clr-dg-cell>
<clr-dg-cell>{{user.menuItemDesc}}</clr-dg-cell>
<clr-dg-cell >{{user.menuId}}</clr-dg-cell>
<clr-dg-cell >{{user.itemSeq}}</clr-dg-cell>
<clr-dg-cell > {{user.moduleName}} </clr-dg-cell>
<clr-dg-cell > {{user.main_menu_action_name}}</clr-dg-cell>
<clr-dg-cell >{{user.status}} </clr-dg-cell>
<clr-dg-cell class="mm-cell mm-cell--action">
<div class="mm-actions-inline">
<button class="btn btn-sm btn-outline mm-btn-edit" (click)="goToEdit(user)"><clr-icon shape="edit"></clr-icon></button>
<button class="btn btn-sm btn-outline mm-btn-delete" (click)="onDelete(user)"><clr-icon shape="trash"></clr-icon></button>
</div>
</clr-dg-cell>
<clr-dg-action-overflow>
<button class="action-item" (click)="goToEdit(user)">Edit <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>
</clr-dg-action-overflow>
<clr-dg-row-detail *clrIfExpanded >
<table class="table">
@@ -111,7 +73,7 @@
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer class="mm-grid-footer">
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
@@ -119,31 +81,8 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
<div *ngIf="viewMode==='cards'">
<div class="mm-cards">
<div class="mm-card-item" *ngFor="let user of filteredSubMenus; let i = index">
<div class="mm-card-item__header">
<!-- <div class="mm-card-item__icon"><clr-icon [shape]="getIconShape(user)"></clr-icon></div> -->
<div class="mm-card-item__title">{{user.menuItemDesc}}</div>
<div class="mm-card-item__badge" [class.mm-badge--success]="user.status==='Enable'" [class.mm-badge--muted]="user.status!=='Enable'">{{user.status}}</div>
</div>
<div class="mm-card-item__body">
<div class="mm-kv"><span>ID</span><strong>{{ user.menuId || '—' }}</strong></div>
<div class="mm-kv"><span>Sequence</span><strong>{{ (user.itemSeq === 0 || user.itemSeq) ? user.itemSeq : '—' }}</strong></div>
<div class="mm-kv"><span>Module</span><strong class="mm-ellipsis">{{ user.moduleName || '—' }}</strong></div>
<div class="mm-kv mm-link"><clr-icon shape="link"></clr-icon><strong class="mm-ellipsis">{{ user.main_menu_action_name || '—' }}</strong></div>
</div>
<div class="mm-card-item__footer">
<button class="btn btn-sm btn-outline mm-btn-edit" (click)="goToEdit(user)"><clr-icon shape="edit"></clr-icon> Edit</button>
<button class="btn btn-sm btn-outline mm-btn-delete" (click)="onDelete(user)"><clr-icon shape="trash"></clr-icon> Delete</button>
</div>
</div>
</div>
</div>
<clr-modal class="mm-modal" [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Add Sub Menu Maintenance</h3>
<div class="modal-body">
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
@@ -242,7 +181,7 @@
<br>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
<button type="submit" class="btn btn-primary">ADD</button>
<button type="submit" class="btn btn-primary" >ADD</button>
</div>
</form>
</div>
@@ -262,7 +201,7 @@
</clr-modal>
<clr-modal class="mm-modal" [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Update Sub Menu Maintenance</h3>
<div class="modal-body" *ngIf="rowSelected.menuItemId">
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
@@ -301,7 +240,7 @@
<br>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
<button type="submit" class="btn btn-primary">Update</button>
<button type="submit" class="btn btn-primary" >Update</button>
</div>
</form>
</div>

View File

@@ -1,88 +1,3 @@
@import '../../../../../styles/design-tokens';
.mm-breadcrumb { margin-bottom: 0.75rem; }
.mm-modal .modal-body { background: var(--theme-surface); }
.mm-modal .modal-title { font-weight: 600; }
.mm-modal .modal-body .clr-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
@media (max-width: 640px) { .mm-modal .modal-body .clr-row { grid-template-columns: 1fr; } }
.mm-modal label { display: inline-block; margin-bottom: 6px; font-weight: 600; color: var(--theme-text); }
.mm-modal input.clr-input, .mm-modal select { width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid rgba(0,0,0,0.12); background: var(--theme-background); transition: border-color 150ms ease, box-shadow 150ms ease; }
.mm-modal input.clr-input:focus, .mm-modal select:focus { outline: none; border-color: var(--theme-primary); box-shadow: 0 0 0 3px var(--theme-primary-100); }
.mm-modal .modal-footer { display: flex; justify-content: flex-end; gap: 10px; }
.mm-modal .modal-footer .btn.btn-primary { background: var(--theme-primary); color: #fff; border-radius: 10px; }
.mm-modal .modal-footer .btn.btn-outline { border-radius: 10px; border: 1px solid var(--theme-secondary); color: var(--theme-secondary); }
.mm-modal .error_mess { color: #ef4444; font-size: 12px; margin-top: 4px; }
.mm-hero {
display: flex; justify-content: space-between; align-items: center;
background: var(--theme-glass-bg); backdrop-filter: blur(12px);
border: 1px solid var(--theme-glass-border);
border-radius: calc(var(--theme-border-radius) + 4px);
padding: 1rem 1.25rem; margin-bottom: 0.75rem; box-shadow: var(--theme-glass-shadow);
}
.mm-hero__content { display: flex; align-items: center; gap: 0.75rem; }
.mm-hero__icon clr-icon { width: 28px; height: 28px; color: var(--theme-primary); }
.mm-hero__title { font-size: 1.25rem; margin: 0; }
.mm-hero__subtitle { margin: 0; font-size: 0.875rem; color: var(--theme-text-secondary); }
.mm-hero__actions .btn + .btn { margin-left: 0.5rem; }
.mm-btn-primary { border-radius: var(--theme-border-radius); background: var(--theme-primary); color: #fff; }
.mm-btn-text { margin-left: 0.35rem; }
.mm-toolbar { position: sticky; top: 0; z-index: 10; display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 8px 10px; margin-bottom: 8px; background: var(--theme-surface); border: 1px solid rgba(0,0,0,0.05); border-radius: var(--theme-border-radius); box-shadow: 0 1px 0 rgba(0,0,0,0.03); }
.mm-search { display: inline-flex; align-items: center; gap: 6px; border: 1px solid rgba(0,0,0,0.08); padding: 6px 8px; border-radius: 999px; background: var(--theme-background); }
.mm-search__input { border: none; outline: none; background: transparent; min-width: 220px; }
.mm-select { border-radius: 999px; padding: 6px 10px; border: 1px solid rgba(0,0,0,0.08); background: var(--theme-background); }
.mm-view-toggle .btn { margin-left: 6px; border-radius: 999px; }
.mm-card { background: var(--theme-glass-bg); backdrop-filter: blur(10px); border: 1px solid var(--theme-glass-border); border-radius: var(--theme-border-radius); padding: 0.25rem; }
.mm-grid { background: var(--theme-surface); border-radius: var(--theme-border-radius); box-shadow: var(--theme-shadow); }
.mm-grid--modern .datagrid-header { background: linear-gradient(135deg, var(--theme-primary-50), transparent); }
.mm-grid-footer { border-top: 1px solid rgba(0,0,0,0.06); }
.mm-row:hover { background: var(--theme-primary-50); }
.mm-item { display: flex; align-items: center; gap: 0.75rem; }
.mm-item__avatar clr-icon { width: 18px; height: 18px; color: var(--theme-accent); }
.mm-item__title { font-weight: 600; }
.mm-item__meta { font-size: 0.75rem; color: var(--theme-text-secondary); }
.mm-actions-inline .btn { margin-right: 6px; }
.mm-actions-inline .mm-btn-edit { border: 1px solid var(--theme-accent); color: var(--theme-accent); }
.mm-actions-inline .mm-btn-edit:hover { background: rgba(139, 92, 246, 0.12); }
.mm-actions-inline .mm-btn-delete { border: 1px solid #ef4444; color: #ef4444; }
.mm-actions-inline .mm-btn-delete:hover { background: rgba(239, 68, 68, 0.12); }
.mm-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 1024px) { .mm-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .mm-cards { grid-template-columns: 1fr; } .mm-search__input { min-width: 140px; } }
.mm-card-item { background: var(--theme-surface); border: 1px solid rgba(0,0,0,0.06); border-radius: var(--theme-border-radius); box-shadow: var(--theme-shadow); overflow: hidden; display: flex; flex-direction: column; }
.mm-card-item__header { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: linear-gradient(135deg, var(--theme-primary-50), transparent); }
.mm-card-item__icon clr-icon { color: var(--theme-primary); width: 18px; height: 18px; }
.mm-card-item__title { font-weight: 700; flex: 1 1 auto; max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mm-card-item__badge { margin-left: auto; }
.mm-card-item__body { padding: 10px 12px; display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; }
@media (max-width: 640px) { .mm-card-item__body { grid-template-columns: 1fr; } }
.mm-kv { display: flex; justify-content: space-between; }
.mm-kv span { color: var(--theme-text-secondary); }
.mm-card-item__footer { padding: 10px 12px; display: flex; gap: 8px; border-top: 1px solid rgba(0,0,0,0.06); }
.mm-card-item__footer .mm-btn-edit { border: 1px solid var(--theme-accent); color: var(--theme-accent); }
.mm-card-item__footer .mm-btn-edit:hover { background: rgba(139, 92, 246, 0.12); }
.mm-card-item__footer .mm-btn-delete { border: 1px solid #ef4444; color: #ef4444; }
.mm-card-item__footer .mm-btn-delete:hover { background: rgba(239, 68, 68, 0.12); }
.mm-badge { display: inline-block; padding: 2px 10px; border-radius: 999px; background: var(--theme-secondary); color: #fff; font-size: 0.75rem; }
.mm-badge--success { background: var(--theme-primary); }
.mm-badge--muted { background: var(--theme-primary-200); color: var(--theme-text); }
/* Modals */
.mm-modal .modal-body { background: var(--theme-surface); }
.mm-modal .modal-title { font-weight: 600; }
.mm-modal .modal-body .clr-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
@media (max-width: 640px) { .mm-modal .modal-body .clr-row { grid-template-columns: 1fr; } }
.mm-modal label { display: inline-block; margin-bottom: 6px; font-weight: 600; color: var(--theme-text); }
.mm-modal input.clr-input, .mm-modal select { width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid rgba(0,0,0,0.12); background: var(--theme-background); transition: border-color 150ms ease, box-shadow 150ms ease; }
.mm-modal input.clr-input:focus, .mm-modal select:focus { outline: none; border-color: var(--theme-primary); box-shadow: 0 0 0 3px var(--theme-primary-100); }
.mm-modal .modal-footer { display: flex; justify-content: flex-end; gap: 10px; }
.mm-modal .modal-footer .btn.btn-primary { background: var(--theme-primary); color: #fff; border-radius: 10px; }
.mm-modal .modal-footer .btn.btn-outline { border-radius: 10px; border: 1px solid var(--theme-secondary); color: var(--theme-secondary); }
.mm-modal .error_mess { color: #ef4444; font-size: 12px; margin-top: 4px; }
input[type=text],[type=date],[type=password],[type=number] {
width: 100%;
padding: 15px 20px;

View File

@@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { ThemeService } from 'src/app/services/theme.service';
import { MenumaintanceService } from '../../../../services/admin/menumaintance.service';
import { Rn_Main_Menu } from '../../../../models/builder/Rn_Main_Menu';
import { ActivatedRoute, Router } from '@angular/router';
@@ -31,22 +30,13 @@ export class SubmenuComponent implements OnInit {
mainid;
submitted = false;
public entryForm: FormGroup;
// UI filter/view state
filterText = '';
statusFilter: 'All' | 'Enable' | 'Disable' | '' = 'All';
viewMode: 'cards' | 'table' = 'cards';
constructor(private menuservice: MenumaintanceService,
private toastr: ToastrService,
private _fb: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private themeService: ThemeService) { }
private router: Router,) { }
ngOnInit(): void {
this.themeService.currentTheme$.subscribe(() => {
// Theme CSS variables applied globally
});
this.id = this.route.snapshot.params["id"];
console.log("project mainmenu with id = ", this.id);
this.getById(this.id);
@@ -65,34 +55,6 @@ export class SubmenuComponent implements OnInit {
// this.getdata();
}
// Derived/filtered list for view
get filteredSubMenus(): any[] {
const items: any[] = (this.sub as unknown as any[]) || [];
const text = (this.filterText || '').toLowerCase();
const status = this.statusFilter;
return items.filter(m => {
const matchText = !text || (
(m.menuItemDesc || '').toLowerCase().includes(text) ||
(m.moduleName || '').toLowerCase().includes(text) ||
(m.main_menu_action_name || '').toLowerCase().includes(text)
);
const matchStatus = !status || status === 'All' || m.status === status;
return matchText && matchStatus;
});
}
setViewMode(mode: 'cards' | 'table') { this.viewMode = mode; }
// Resolve a safe icon
getIconShape(menu: any): string {
const raw = (menu?.main_menu_icon_name ?? menu?.mainMenuIconName ?? '').toString().trim();
const name = raw.toLowerCase();
const invalid = !name || name === 'undefined' || name === 'null' || name === '-' || name === 'na' || name === 'n/a';
if (invalid) return 'file';
const alias: Record<string, string> = { application:'application', applications:'applications', settings:'cog', config:'cog', user:'user', users:'users', folder:'folder', file:'file', tag:'tag', bookmark:'bookmark', home:'home', dashboard:'dashboard', menu:'list', list:'list', link:'link', module:'application' };
return alias[name] ?? name;
}
getById(id: any) {
this.menuservice.getbyid(id).subscribe((data) => {
this.sub = data;

View File

@@ -1,71 +1,47 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon>{{ 'SECURITY' | translate }}</a></li>
<li><a href="javascript://">{{'USER_GROUP_MAINTENANCE' | translate}}</a></li>
</ol>
<section class="ug-hero">
<div class="ug-hero__content">
<div class="ug-hero__icon"><clr-icon shape="users"></clr-icon></div>
<div>
<h2 class="ug-hero__title">{{'USER_GROUP_MAINTENANCE' | translate}}</h2>
<p class="ug-hero__subtitle">{{ 'SECURITY' | translate }}</p>
<div class="dg-wrapper">
<div class="clr-row">
<div class="clr-col">
<h3>{{'USER_GROUP_MAINTENANCE' | translate}}</h3>
</div>
</div>
<div class="ug-hero__actions">
<button class="ug-btn ug-btn-secondary" (click)="downloadFiles()">
<clr-icon shape="download"></clr-icon>
<span class="ug-btn-text">{{ 'DOWNLOAD_TEMPLATE' | translate }}</span>
</button>
<button class="ug-btn ug-btn-secondary" (click)="csvImport()">
<clr-icon shape="import"></clr-icon>
<span class="ug-btn-text">{{ 'IMPORT' | translate }}</span>
</button>
<button class="ug-btn ug-btn-secondary" (click)="onExport()">
<clr-icon shape="export"></clr-icon>
<span class="ug-btn-text">{{ 'EXPORT_XLSX' | translate }}</span>
</button>
<button id="add" class="ug-btn ug-btn-primary" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon>
<span class="ug-btn-text">{{ 'ADD' | translate }}</span>
</button>
<div class="clr-col" style="text-align: right;">
<div class="btn-group">
<button class="btn text-dark" (click)="downloadFiles()"><b>{{ 'DOWNLOAD_TEMPLATE' | translate }}</b></button>
</div>
</section>
<div class="ug-toolbar">
<div class="ug-toolbar__left">
<div class="ug-search">
<clr-icon shape="search"></clr-icon>
<input class="ug-search__input" type="text" [placeholder]="'SEARCH' | translate" [(ngModel)]="filterText" />
</div>
</div>
<div class="ug-toolbar__right">
<div class="ug-view-toggle">
<button class="ug-btn ug-btn-sm" [class.ug-btn-primary]="viewMode==='cards'" (click)="setViewMode('cards')">
<clr-icon shape="view-cards"></clr-icon>
<button class="btn btn-primary" (click)="csvImport()">{{ 'IMPORT' | translate }}</button>
<button class="btn btn-outline" (click)="onExport()">
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
</button>
<button class="ug-btn ug-btn-sm" [class.ug-btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
<clr-icon shape="table"></clr-icon>
<button id="add" class="btn btn-primary" *ngIf="mcreate == 'true'" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
</button>
</div>
</div>
</div>
<clr-datagrid class="ug-grid" [clrDgLoading]="loading" *ngIf="viewMode==='table'">
<clr-dg-placeholder>
<ng-template #loadingSpinner>
<clr-spinner>{{'LOADING' | translate}} ... </clr-spinner>
</ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div>
</clr-dg-placeholder>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder> <ng-template #loadingSpinner><clr-spinner>{{'LOADING' | translate}} ... </clr-spinner></ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'usrGrp'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'USERGROUP_NO' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'groupName'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'GROUP_NAME' | translate}}
@@ -92,32 +68,19 @@
</ng-container>
</clr-dg-column>
<clr-dg-row *clrDgItems="let user of filteredGroups;let i = index" [clrDgItem]="user">
<clr-dg-row *clrDgItems="let user of givendata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{user.usrGrp}}</clr-dg-cell>
<clr-dg-cell>{{user.groupName}}</clr-dg-cell>
<clr-dg-cell>{{user.groupDesc}}</clr-dg-cell>
<clr-dg-cell >{{user.groupDesc}}</clr-dg-cell>
<clr-dg-cell>{{user.groupLevel}}</clr-dg-cell>
<clr-dg-cell>
<span class="ug-status-badge" [class.ug-status-enabled]="user.status === 'E'"
[class.ug-status-disabled]="user.status === 'D'">
{{user.status === 'E' ? 'Enabled' : 'Disabled'}}
</span>
</clr-dg-cell>
<clr-dg-cell>{{user.status}}</clr-dg-cell>
<!-- <clr-dg-cell>{{user.usrGrp}}</clr-dg-cell> -->
<clr-dg-cell>{{user.updateDateFormated}}</clr-dg-cell>
<clr-dg-action-overflow>
<!-- <button class="ug-action-item" *ngIf="medit == 'true' || medit == true" (click)="goToEdit(user)">
<clr-icon shape="edit"></clr-icon>
{{'EDIT' | translate}}
</button> -->
<button class="ug-action-item" (click)="goToEdit(user)">
<clr-icon shape="edit"></clr-icon>
{{'EDIT' | translate}}
</button>
<button class="ug-action-item ug-btn-error" (click)="onDelete(user)">
<clr-icon shape="trash"></clr-icon>
{{'DELETE'|translate}}
</button>
<button class="action-item" *ngIf="medit == 'true'" (click)="goToEdit(user)"> {{'EDIT' | translate}}</button>
<button class="action-item" (click)="onDelete(user)">{{'DELETE'|translate}}<clr-icon shape="trash" class="is-error"></clr-icon></button>
</clr-dg-action-overflow>
<clr-dg-row-detail *clrIfExpanded>
@@ -126,195 +89,136 @@
<td class="td-title">username</td>
<td class="td-content">{{user.groupName}}</td>
</tr>
</table>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer class="ug-grid-footer">
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Record per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}} Records
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<div class="ug-cards" *ngIf="viewMode==='cards'">
<div class="ug-card-item" *ngFor="let user of filteredGroups; let i = index">
<div class="ug-card-item__header">
<div class="ug-card-item__icon"><clr-icon shape="users"></clr-icon></div>
<div class="ug-card-item__title">{{user.groupName}}</div>
<div class="ug-card-item__badge" [class.ug-status-enabled]="user.status === 'E'"
[class.ug-status-disabled]="user.status === 'D'">
{{user.status === 'E' ? 'Enabled' : 'Disabled'}}
</div>
</div>
<div class="ug-card-item__body">
<div class="ug-kv"><span>ID</span><strong>{{user.usrGrp}}</strong></div>
<div class="ug-kv"><span>Desc</span><strong>{{user.groupDesc}}</strong></div>
<div class="ug-kv"><span>Level</span><strong>{{user.groupLevel}}</strong></div>
<div class="ug-kv"><span>Updated</span><strong>{{user.updateDateFormated}}</strong></div>
</div>
<div class="ug-card-item__footer">
<!-- <button class="ug-btn ug-btn-sm ug-btn-outline" *ngIf="medit == 'true' || medit == true" (click)="goToEdit(user)">
<clr-icon shape="edit"></clr-icon> Edit
</button> -->
<button class="ug-btn ug-btn-sm ug-btn-outline" (click)="goToEdit(user)">
<clr-icon shape="edit"></clr-icon> Edit
</button>
<button class="ug-btn ug-btn-sm ug-btn-error" (click)="onDelete(user)">
<clr-icon shape="trash"></clr-icon> Delete
</button>
</div>
</div>
</clr-datagrid>
</div>
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Add User Group Maintenance</h3>
<div class="modal-body">
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
<div class="ug-form-group">
<label for="groupName" class="ug-form-label">User Group<span class="required-field">*</span></label>
<input type="text" id="groupName" class="ug-form-input" formControlName="groupName">
<div *ngIf="submitted && entryForm.controls.groupName.errors" class="ug-form-error">
<div *ngIf="submitted && entryForm.controls.groupName.errors.required">*This field is Required</div>
<div class="clr-row">
<div class="clr-col-sm-12">
<label for="name">User Group<span class="required-field">*</span></label>
<input type="text" class="clr-input" formControlName="groupName">
<div *ngIf="submitted && entryForm.controls.groupName.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.groupName.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="ug-form-group">
<label for="groupDesc" class="ug-form-label">Description<span class="required-field">*</span></label>
<input type="text" id="groupDesc" class="ug-form-input" formControlName="groupDesc">
<div *ngIf="submitted && entryForm.controls.groupDesc.errors" class="ug-form-error">
<div *ngIf="submitted && entryForm.controls.groupDesc.errors.required">*This field is Required</div>
<div class="clr-col-sm-12">
<label for="name">Description<span class="required-field">*</span></label>
<input type="text" class="clr-input" formControlName="groupDesc">
<div *ngIf="submitted && entryForm.controls.groupDesc.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.groupDesc.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="ug-form-group">
<label for="status" class="ug-form-label">Status<span class="required-field">*</span></label>
<select id="status" class="ug-form-select" formControlName="status">
<option value="">Select Status</option>
<div class="clr-col-sm-12">
<label for="tags">Status<span class="required-field">*</span></label>
<select id="" formControlName="status">
<option value="E">Enable</option>
<option value="D">Disable</option>
<option value="D">Disable</option></select>
<div *ngIf="submitted && entryForm.controls.status.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.status.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="clr-col-sm-12">
<label for="tags">Group Level<span class="required-field">*</span></label>
<select id="" formControlName="groupLevel">
<option >10</option>
<option >20</option>
<option >30</option>
<option >40</option>
<option>50</option>
</select>
<div *ngIf="submitted && entryForm.controls.status.errors" class="ug-form-error">
<div *ngIf="submitted && entryForm.controls.status.errors.required">*This field is Required</div>
<div *ngIf="submitted && entryForm.controls.groupLevel.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.groupLevel.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="ug-form-group">
<label for="groupLevel" class="ug-form-label">Group Level<span class="required-field">*</span></label>
<select id="groupLevel" class="ug-form-select" formControlName="groupLevel">
<option value="">Select Level</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="40">40</option>
<option value="50">50</option>
</select>
<div *ngIf="submitted && entryForm.controls.groupLevel.errors" class="ug-form-error">
<div *ngIf="submitted && entryForm.controls.groupLevel.errors.required">*This field is Required</div>
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="ug-btn ug-btn-outline" (click)="modalAdd = false">Cancel</button>
<button type="submit" class="ug-btn ug-btn-primary">ADD</button>
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
<button type="submit" class="btn btn-primary" >ADD</button>
</div>
</form>
</form>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Update User Group Maintenance</h3>
<div class="modal-body" *ngIf="rowSelected.usrGrp">
<div class="ug-form-group">
<label for="editUsrGrp" class="ug-form-label">User Group ID</label>
<input type="text" id="editUsrGrp" class="ug-form-input" [(ngModel)]="rowSelected.usrGrp" name="usrGrp" disabled>
<h2 class="heading">{{rowSelected.usrGrp}}</h2>
<form (ngSubmit)="onUpdate(rowSelected.usrGrp)">
<div class="clr-row">
<div class="clr-col-sm-12">
<label for="name">User Group<span class="required-field">*</span></label>
<input type="text" class="clr-input" [(ngModel)]="rowSelected.groupName" name="groupName">
</div>
<div class="ug-form-group">
<label for="editGroupName" class="ug-form-label">User Group<span class="required-field">*</span></label>
<input type="text" id="editGroupName" class="ug-form-input" [(ngModel)]="rowSelected.groupName" name="groupName">
<div class="clr-col-sm-12">
<label for="name">Description<span class="required-field">*</span></label>
<input type="text" class="clr-input" [(ngModel)]="rowSelected.groupDesc" name="groupDesc">
</div>
<div class="ug-form-group">
<label for="editGroupDesc" class="ug-form-label">Description<span class="required-field">*</span></label>
<input type="text" id="editGroupDesc" class="ug-form-input" [(ngModel)]="rowSelected.groupDesc" name="groupDesc">
</div>
<div class="ug-form-group">
<label for="editStatus" class="ug-form-label">Status<span class="required-field">*</span></label>
<select id="editStatus" class="ug-form-select" [(ngModel)]="rowSelected.status" name="status">
<div class="clr-col-sm-12">
<label for="tags">Status<span class="required-field">*</span></label>
<select id="" [(ngModel)]="rowSelected.status" name="status">
<option value="E">Enable</option>
<option value="D">Disable</option>
<option value="D">Disable</option></select>
</div>
<div class="clr-col-sm-12">
<label for="tags">Group Level<span class="required-field">*</span></label>
<select id="" [(ngModel)]="rowSelected.groupLevel" name="groupLevel">
<option >10</option>
<option >20</option>
<option >30</option>
<option >40</option>
<option>50</option>
</select>
</div>
<div class="ug-form-group">
<label for="editGroupLevel" class="ug-form-label">Group Level<span class="required-field">*</span></label>
<select id="editGroupLevel" class="ug-form-select" [(ngModel)]="rowSelected.groupLevel" name="groupLevel">
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="40">40</option>
<option value="50">50</option>
</select>
</div>
<br>
<div class="modal-footer">
<button type="button" class="ug-btn ug-btn-outline" (click)="modaledit = false">Cancel</button>
<button type="submit" (click)="onUpdate(rowSelected.usrGrp)" class="ug-btn ug-btn-primary">Update</button>
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
<button type="submit" class="btn btn-primary" >Update</button>
</div>
</form>
</div>
</clr-modal>
</clr-modal>
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body" *ngIf="rowSelected.usrGrp">
<div class="ug-delete-header">
<div class="ug-delete-icon">
<clr-icon shape="exclamation-triangle" size="48"></clr-icon>
</div>
<h1 class="ug-delete-title">Are You Sure Want to Delete?</h1>
<p class="ug-delete-subtitle">This action cannot be undone</p>
</div>
<div class="ug-delete-details">
<div class="ug-delete-detail-item">
<span class="ug-delete-detail-label">User Group ID:</span>
<span class="ug-delete-detail-value">{{rowSelected.usrGrp}}</span>
</div>
<div class="ug-delete-detail-item">
<span class="ug-delete-detail-label">Group Name:</span>
<span class="ug-delete-detail-value">{{rowSelected.groupName}}</span>
</div>
<div class="ug-delete-detail-item">
<span class="ug-delete-detail-label">Description:</span>
<span class="ug-delete-detail-value">{{rowSelected.groupDesc}}</span>
</div>
</div>
<h1 class="delete">Are You Sure Want to delete?</h1>
<h2 class="heading">{{rowSelected.usrGrp}}</h2>
<div class="modal-footer">
<button type="button" class="ug-btn ug-btn-outline" (click)="modaldelete = false">Cancel</button>
<button type="submit" (click)="delete(rowSelected.usrGrp)" class="ug-btn ug-btn-error">Delete</button>
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
<button type="submit" (click)="delete(rowSelected.usrGrp)" class="btn btn-primary" >Delete</button>
</div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
<h3 class="modal-title">Import File</h3>
<div class="modal-body">
<div class="ug-form-group">
<label class="ug-form-label">Select CSV File</label>
<input type="file" name="file" class="ug-file-input" (change)="selectFile($event)"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
</div>
<input type="file" name="file" class="file" (change)="selectFile($event)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
</div>
<div class="modal-footer">
<button class="ug-btn ug-btn-outline" type="button" (click)="modalCsv = false">Cancel</button>
<button class="ug-btn ug-btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
</div>
</clr-modal>

View File

@@ -1,748 +1,8 @@
@import '../../../../../styles.scss';
// Import design tokens
@import '../../../../../styles/design-tokens';
// User Group Maintenance Styles
.ug-hero {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px;
margin-bottom: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
font-family: var(--theme-font-primary);
&__content {
display: flex;
align-items: center;
gap: 16px;
}
&__icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
clr-icon {
width: 36px;
height: 36px;
color: white;
}
}
&__title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: white;
font-family: var(--theme-font-secondary);
}
&__subtitle {
font-size: 16px;
margin: 0;
opacity: 0.9;
color: rgba(255, 255, 255, 0.8);
}
&__actions {
display: flex;
gap: 12px;
}
@import '../../../../../styles1.scss';
input.ng-invalid.ng-touched {
border-color: red;
}
.ug-btn-text {
margin-left: 8px;
}
// Modern Button Styles using ThemeService
.ug-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
line-height: 1;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
transition: all 200ms ease-out;
text-decoration: none;
position: relative;
overflow: hidden;
font-family: var(--theme-font-primary);
z-index: 1;
&:focus {
outline: 2px solid var(--theme-primary);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
// Sizes
&.ug-btn-sm {
padding: 8px 16px;
font-size: 13px;
height: 32px;
}
&.ug-btn-md {
padding: 12px 20px;
font-size: 14px;
height: 40px;
}
&.ug-btn-lg {
padding: 16px 24px;
font-size: 16px;
height: 48px;
}
// Variants
&.ug-btn-primary {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-color: var(--theme-primary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: linear-gradient(135deg, var(--theme-primary, #0284c7) 0%, var(--theme-accent, #7c3aed) 100%);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
&:active {
transform: translateY(0);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
}
&.ug-btn-secondary {
background: var(--theme-surface);
color: var(--theme-text);
border-color: rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-background);
border-color: rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.ug-btn-outline {
background: transparent;
color: var(--theme-secondary);
border-color: var(--theme-secondary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: rgba(100, 116, 139, 0.1); // var(--theme-secondary) with 10% opacity
border-color: var(--theme-secondary);
color: var(--theme-secondary);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.ug-btn-error {
background: var(--theme-error, #ef4444);
color: white;
border-color: var(--theme-error, #ef4444);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-error-dark, #dc2626);
border-color: var(--theme-error-dark, #dc2626);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.3), 0 4px 6px -2px rgba(239, 68, 68, 0.1);
}
}
&.ug-btn-ghost {
background: transparent;
color: var(--theme-text-secondary);
border-color: transparent;
&:hover {
background: var(--theme-background);
color: var(--theme-text);
}
}
}
.ug-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 16px;
background: var(--theme-surface);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&__left {
flex: 1;
}
&__right {
display: flex;
gap: 8px;
}
}
.ug-search {
position: relative;
max-width: 400px;
clr-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
z-index: 1;
}
&__input {
width: 100%;
padding: 12px 12px 12px 40px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
font-size: 14px;
transition: all 200ms ease-out;
background: var(--theme-surface);
color: var(--theme-text);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1); // var(--theme-primary) with 10% opacity
}
}
}
.ug-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 8px;
overflow: hidden;
.ug-btn {
border-radius: 0;
border: none;
background: var(--theme-surface);
color: var(--theme-text-secondary);
&:first-child {
border-right: 1px solid #d1d5db;
}
&.ug-btn-primary {
background: var(--theme-primary);
color: white;
}
}
}
// Modern Table Styles
.ug-grid {
background: var(--theme-surface);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
::ng-deep .datagrid {
.datagrid-head {
background: var(--theme-background);
.datagrid-column {
padding: 16px 24px;
font-size: 12px;
font-weight: 600;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e5e7eb;
}
}
.datagrid-row {
transition: background-color 150ms ease-out;
&:hover {
background: var(--theme-background);
}
&:not(:last-child) {
border-bottom: 1px solid #e5e7eb;
}
.datagrid-cell {
padding: 16px 24px;
font-size: 14px;
color: var(--theme-text);
}
}
.datagrid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
padding: 16px 24px;
}
}
}
.ug-grid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
}
// Modern Card Styles - More beautiful design
.ug-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
margin-bottom: 24px;
}
.ug-card-item {
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--theme-border-radius); // Using theme variable
box-shadow: var(--theme-shadow); // Using theme variable
overflow: hidden;
display: flex;
flex-direction: column;
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-5px);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
border-color: rgba(14, 165, 233, 0.3); // var(--theme-primary) with 30% opacity
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 16px;
background: linear-gradient(135deg, rgba(14, 165, 233, 0.1), transparent); // var(--theme-primary) with 10% opacity
min-height: 60px;
}
&__icon {
clr-icon {
width: 24px;
height: 24px;
color: var(--theme-primary);
}
}
&__title {
font-weight: 700;
flex: 1 1 auto;
font-size: 18px;
color: var(--theme-text);
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__badge {
margin-left: auto;
display: inline-flex;
align-items: center;
padding: 4px 12px;
font-size: 12px;
font-weight: 600;
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
&__body {
padding: 16px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
&__footer {
padding: 16px;
display: flex;
gap: 8px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
background: var(--theme-background);
}
}
.ug-kv {
display: flex;
justify-content: space-between;
font-size: 14px;
span {
color: var(--theme-text-secondary);
}
strong {
color: var(--theme-text);
font-weight: 500;
text-align: right;
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
}
}
// Theme the card footer buttons
.ug-card-item__footer .ug-btn {
border-radius: 10px;
flex: 1;
justify-content: center;
}
.ug-card-item__footer .ug-btn.ug-btn-outline {
border: 1px solid var(--theme-secondary);
color: var(--theme-secondary);
background: transparent;
&:hover {
background: rgba(100, 116, 139, 0.1); // var(--theme-secondary) with 10% opacity
border-color: var(--theme-secondary);
color: var(--theme-secondary);
}
}
.ug-card-item__footer .ug-btn.ug-btn-error {
background: var(--theme-error, #ef4444);
color: #fff;
border-color: var(--theme-error, #ef4444);
&:hover {
background: var(--theme-error-dark, #dc2626);
border-color: var(--theme-error-dark, #dc2626);
transform: translateY(-2px);
}
}
// Table view action buttons
.ug-action-item {
@extend .ug-btn;
@extend .ug-btn-ghost;
@extend .ug-btn-sm;
width: 100%;
justify-content: flex-start;
margin-bottom: 4px;
text-align: left;
clr-icon {
width: 16px;
height: 16px;
margin-right: 8px;
}
&.ug-btn-error {
color: var(--theme-error, #ef4444);
&:hover {
background: rgba(239, 68, 68, 0.1);
}
}
}
// Ensure action overflow is visible
::ng-deep .datagrid-action-overflow {
button.action-item {
display: flex;
align-items: center;
width: 100%;
text-align: left;
padding: 8px 12px;
clr-icon {
margin-right: 8px;
}
}
}
// Status Badges
.ug-status-badge {
display: inline-flex;
align-items: center;
padding: 6px 16px;
font-size: 13px;
font-weight: 600;
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.ug-status-enabled {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(16, 185, 129, 0.05) 100%);
color: #10b981;
}
.ug-status-disabled {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%);
color: #ef4444;
}
// Form Styles
.ug-form-group {
margin-bottom: 20px;
}
.ug-form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--theme-text);
margin-bottom: 8px;
font-family: var(--theme-font-primary);
}
.ug-form-input, .ug-form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
line-height: 1.5;
color: var(--theme-text);
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
transition: all 200ms ease-out;
margin-bottom: 0;
font-family: var(--theme-font-primary);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1); // var(--theme-primary) with 10% opacity
}
&:disabled {
background: var(--theme-background);
color: var(--theme-text-secondary);
cursor: not-allowed;
}
&.error {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
&::placeholder {
color: var(--theme-text-secondary);
}
}
.ug-form-select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 12px center;
background-repeat: no-repeat;
background-size: 16px 12px;
padding-right: 40px;
appearance: none;
}
.ug-form-error {
color: #ef4444;
font-size: 12px;
margin-top: 6px;
font-weight: 500;
}
.required-field {
color: #ef4444;
margin-left: 4px;
}
.ug-file-input {
width: 100%;
padding: 12px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
background: var(--theme-surface);
color: var(--theme-text);
&::file-selector-button {
@extend .ug-btn;
@extend .ug-btn-secondary;
margin-right: 12px;
padding: 8px 16px;
border: none;
}
}
// Delete Modal Styles
.ug-delete-header {
text-align: center;
padding: 24px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.ug-delete-icon {
color: #ef4444;
margin-bottom: 16px;
}
.ug-delete-title {
font-size: 24px;
font-weight: 700;
color: var(--theme-text);
margin: 0 0 8px 0;
}
.ug-delete-subtitle {
font-size: 16px;
color: var(--theme-text-secondary);
margin: 0;
}
.ug-delete-details {
padding: 24px;
}
.ug-delete-detail-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:last-child {
border-bottom: none;
}
}
.ug-delete-detail-label {
font-weight: 500;
color: var(--theme-text-secondary);
}
.ug-delete-detail-value {
font-weight: 600;
color: var(--theme-text);
}
.delete {
color: #ef4444;
text-align: center;
margin-bottom: 16px;
font-weight: 600;
font-size: 20px;
}
.heading {
text-align: center;
margin-bottom: 24px;
color: var(--theme-text);
font-weight: 600;
}
// Modal Header
::ng-deep .modal-header {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px 16px 0 0 !important;
padding: 20px 24px !important;
.modal-title {
color: white;
font-weight: 600;
font-size: 20px;
margin: 0;
}
.close {
color: white;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
// Modal Body
::ng-deep .modal-body {
padding: 24px !important;
}
// Modal Footer
::ng-deep .modal-footer {
padding: 20px 24px !important;
background: var(--theme-background);
border-radius: 0 0 16px 16px !important;
border-top: 1px solid rgba(0, 0, 0, 0.05) !important;
}
// Responsive adjustments
@media (max-width: 768px) {
.ug-hero {
flex-direction: column;
gap: 16px;
text-align: center;
&__content {
flex-direction: column;
text-align: center;
}
&__actions {
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
}
.ug-toolbar {
flex-direction: column;
gap: 16px;
}
.ug-search {
max-width: 100%;
}
.ug-cards {
grid-template-columns: 1fr;
}
.ug-card-item {
&__header {
flex-direction: column;
text-align: center;
&__icon {
margin-right: 0;
margin-bottom: 16px;
}
}
&__footer {
justify-content: center;
}
}
.error_mess {
color: red;
}

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ExcelService } from '../../../../services/excel.service';
@@ -8,15 +8,12 @@ import { ToastrService } from 'ngx-toastr';
import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
import { CsvService } from 'src/app/services/csv.service';
import { TranslateService } from '@ngx-translate/core';
import { ThemeService } from 'src/app/services/theme.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-usergrpmaintenance',
templateUrl: './usergrpmaintenance.component.html',
styleUrls: ['./usergrpmaintenance.component.scss']
})
export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
export class UsergrpmaintenanceComponent implements OnInit {
loading = false;
public entryForm: FormGroup;
givendata;
@@ -26,13 +23,10 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
modaledit=false;
modaldelete=false;
rowSelected :any= {};
mcreate: string | boolean = false;
medit: string | boolean = false;
showdata: any;
mcreate;
medit;
showdata;
submitted=false;
filterText: string = '';
viewMode: 'table' | 'cards' = 'cards';
private themeSubscription: Subscription;
constructor(
private excel: ExcelService,
@@ -44,32 +38,21 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
private mainservice:UsergrpmaintainceService,
private csvService: CsvService,
private translate: TranslateService,
private themeService: ThemeService,
) { }
switchLanguage(language: string) {
this.translate.use(language);
}
ngOnInit(): void {
this.themeSubscription = this.themeService.currentTheme$.subscribe(() => {
// Theme changes will automatically update CSS variables
// This triggers a re-render of themed elements
});
this.showdata = this.menuGroupService.getdata();
console.log('Showdata:', this.showdata);
if (this.showdata) {
// Handle both string and boolean values
this.mcreate = this.showdata.mcreate === 'true' || this.showdata.mcreate === true ? true : false;
this.medit = this.showdata.medit === 'true' || this.showdata.medit === true ? true : false;
console.log('MCREATE:', this.mcreate);
console.log('MEDIT:', this.medit);
}
console.log(this.showdata);
this.mcreate=this.showdata.mcreate;
console.log(this.mcreate);
this.medit=this.showdata.medit
console.log(this.medit);
this.mainservice.getAll().subscribe((data) => {
console.log('Data received:', data);
this.givendata = data || [];
console.log(data);
this.givendata = data;
if(this.givendata.length==0){
this.error="No data Available";
console.log(this.error)
@@ -89,30 +72,6 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
});
}
ngOnDestroy(): void {
if (this.themeSubscription) {
this.themeSubscription.unsubscribe();
}
}
get totalGroups(): number {
const list: any[] = (this.givendata as unknown as any[]) || [];
return list.length;
}
get filteredGroups(): any[] {
const items: any[] = (this.givendata as unknown as any[]) || [];
const text = (this.filterText || '').toLowerCase();
if (!text) { return items; }
return items.filter(g => (
(g?.groupName || '').toLowerCase().includes(text) ||
(g?.groupDesc || '').toLowerCase().includes(text)
));
}
setViewMode(mode: 'table' | 'cards') { this.viewMode = mode; }
onExport() {
this.excel.exportAsExcelFile(this.givendata, 'user_',
moment().format('YYYYMMDD_HHmmss'))
@@ -122,8 +81,7 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
//this.router.navigate(["../usermaintanceadd"],{relativeTo:this.route});
}
goToEdit(row){
console.log('Edit clicked for row:', row);
this.rowSelected = {...row}; // Create a copy to avoid reference issues
this.rowSelected = row;
this.modaledit=true;
//this.router.navigate(["../usermaintanceedit/"+ id], { relativeTo: this.route });
}
@@ -177,8 +135,7 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
// }
}
onDelete(row) {
console.log('Delete clicked for row:', row);
this.rowSelected = {...row}; // Create a copy to avoid reference issues
this.rowSelected = row;
this.modaldelete=true;
}

View File

@@ -1,108 +1,80 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon>{{ 'SECURITY' | translate }}</a></li>
<li><a href="javascript://">{{'USER_MAINTENANCE'| translate}}</a></li>
</ol>
<section class="um-hero">
<div class="um-hero__content">
<div class="um-hero__icon"><clr-icon shape="user"></clr-icon></div>
<div>
<h2 class="um-hero__title">{{'USER_MAINTENANCE'| translate}}</h2>
<p class="um-hero__subtitle">{{ 'SECURITY' | translate }}</p>
</div>
</div>
<div class="um-hero__actions">
<button class="um-btn um-btn-secondary" (click)="downloadFiles()">
<clr-icon shape="download"></clr-icon>
<span class="um-btn-text">{{ 'DOWNLOAD_TEMPLATE' | translate }}</span>
</button>
<button class="um-btn um-btn-secondary" (click)="csvImport()">
<clr-icon shape="import"></clr-icon>
<span class="um-btn-text">{{ 'IMPORT' | translate }}</span>
</button>
<button class="um-btn um-btn-secondary" (click)="onExport()">
<clr-icon shape="export"></clr-icon>
<span class="um-btn-text">{{ 'EXPORT_XLSX' | translate }}</span>
</button>
<button id="add" class="um-btn um-btn-primary" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon>
<span class="um-btn-text">{{ 'ADD' | translate }}</span>
</button>
<!-- <button id="add" class="um-btn um-btn-primary" *ngIf="mcreate == 'true' || mcreate == true" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon>
<span class="um-btn-text">{{ 'ADD' | translate }}</span>
</button> -->
</div>
</section>
<div class="um-toolbar">
<div class="um-toolbar__left">
<div class="um-search">
<clr-icon shape="search"></clr-icon>
<input class="um-search__input" type="text" [placeholder]="'SEARCH' | translate" [(ngModel)]="filterText" />
<div class="dg-wrapper">
<div class="clr-row">
<div class="clr-col">
<h3>{{'USER_MAINTENANCE'| translate}} </h3>
</div>
</div>
<div class="um-toolbar__right">
<div class="um-view-toggle">
<button class="um-btn um-btn-sm" [class.um-btn-primary]="viewMode==='cards'" (click)="setViewMode('cards')">
<clr-icon shape="view-cards"></clr-icon>
</button>
<button class="um-btn um-btn-sm" [class.um-btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
<clr-icon shape="table"></clr-icon>
</button>
</div>
</div>
</div>
<div class="clr-col" style="text-align: right;">
<div class="btn-group">
<button class="btn text-dark" (click)="downloadFiles()"><b>{{ 'DOWNLOAD_TEMPLATE' | translate }}</b></button>
<clr-datagrid class="um-grid" [clrDgLoading]="loading" *ngIf="viewMode==='table'">
<clr-dg-placeholder>
<ng-template #loadingSpinner>
<clr-spinner>{{'LOADING' | translate}} ... </clr-spinner>
</ng-template>
</div>
<button class="btn btn-primary" (click)="csvImport()">{{ 'IMPORT' | translate }}</button>
<button class="btn btn-outline" (click)="onExport()">
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
</button>
<button id="add" class="btn btn-primary" *ngIf="mcreate == 'true'" (click)="goToAdd()">
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
</button>
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder >
<ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div>
</clr-dg-placeholder>
<!-- <clr-dg-placeholder *ngIf="!loading1;" > <clr-spinner>not</clr-spinner></clr-dg-placeholder>
<ng-template #other_content style="text-align: center;">Data Not found...</ng-template> -->
<clr-dg-column [clrDgField]="'userId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'USER_ID' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'fullName'" style="width: 200px;">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'FULL_NAME' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'email'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'EMAIL' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'usrGrpName'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{'USER_GROUP_NAME' | translate}}
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'userId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
User Id
</ng-container></clr-dg-column>
<!-- <clr-dg-column [clrDgField]="'username'" style="width: 300px;"> <ng-container *clrDgHideableColumn="{hidden: false}">
User Name
</ng-container></clr-dg-column> -->
<clr-dg-column [clrDgField]="'fullName'" style="width: 200px;"><ng-container *clrDgHideableColumn="{hidden: false}">
Full Name
</ng-container></clr-dg-column >
<clr-dg-column [clrDgField]="'email'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Email
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'usrGrpName'"> <ng-container *clrDgHideableColumn="{hidden: false}">
User Group Name
</ng-container></clr-dg-column>
<!-- <clr-dg-column [clrDgField]="'customerNumer'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Customer Number
</ng-container></clr-dg-column> -->
<clr-dg-row *clrDgItems="let user of filteredUsers;let i = index" [clrDgItem]="user">
<clr-dg-row *clrDgItems="let user of givendata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{user.userId}}</clr-dg-cell>
<clr-dg-cell>{{user.fullName}}</clr-dg-cell>
<!-- <clr-dg-cell>{{user.username}}</clr-dg-cell> -->
<clr-dg-cell >{{user.fullName}}</clr-dg-cell>
<clr-dg-cell>{{user.email}}</clr-dg-cell>
<clr-dg-cell>{{user.usrGrpName}}</clr-dg-cell>
<!-- <clr-dg-cell>{{user.customerNumer}}</clr-dg-cell> -->
<clr-dg-action-overflow>
<!-- <button class="um-action-item" *ngIf="medit == 'true' || medit == true" (click)="goToEdit(user.userId)">
<clr-icon shape="edit"></clr-icon>
{{'EDIT' | translate}}
</button> -->
<button class="um-action-item" (click)="goToEdit(user.userId)">
<clr-icon shape="edit"></clr-icon>
{{'EDIT' | translate}}
</button>
<button class="um-action-item um-btn-error" (click)="onDelete(user)">
<clr-icon shape="trash"></clr-icon>
{{'DELETE'|translate}}
</button>
<button class="action-item" *ngIf="medit == 'true'" (click)="goToEdit(user.userId)">Edit</button>
<button class="action-item" (click)="onDelete(user)">Delete<cds-icon shape="trash" class="is-error"></cds-icon></button>
</clr-dg-action-overflow>
<clr-dg-row-detail *clrIfExpanded>
@@ -111,92 +83,42 @@
<td class="td-title">username</td>
<td class="td-content">{{user.username}}</td>
</tr>
</table>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer class="um-grid-footer">
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Record per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}} Records
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<div class="um-cards" *ngIf="viewMode==='cards'">
<div class="um-card-item" *ngFor="let user of filteredUsers; let i = index">
<div class="um-card-item__header">
<div class="um-card-item__icon"><clr-icon shape="user"></clr-icon></div>
<div class="um-card-item__title">{{user.fullName}}</div>
<div class="um-card-item__badge">ID: {{user.userId}}</div>
</div>
<div class="um-card-item__body">
<div class="um-kv"><span>{{'EMAIL' | translate}}</span><strong>{{user.email}}</strong></div>
<div class="um-kv"><span>{{'GROUP' | translate}}</span><strong>{{user.usrGrpName}}</strong></div>
</div>
<div class="um-card-item__footer">
<!-- <button class="um-btn um-btn-sm um-btn-outline" *ngIf="medit == 'true' || medit == true"
(click)="goToEdit(user.userId)">
<clr-icon shape="edit"></clr-icon> {{'EDIT' | translate}}
</button> -->
<button class="um-btn um-btn-sm um-btn-outline" (click)="goToEdit(user.userId)">
<clr-icon shape="edit"></clr-icon> {{'EDIT' | translate}}
</button>
<button class="um-btn um-btn-sm um-btn-error" (click)="onDelete(user)">
<clr-icon shape="trash"></clr-icon> {{'DELETE' | translate}}
</button>
</div>
</div>
</clr-datagrid>
</div>
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body" *ngIf="rowSelected.userId">
<div class="um-delete-header">
<div class="um-delete-icon">
<clr-icon shape="exclamation-triangle" size="48"></clr-icon>
</div>
<h1 class="um-delete-title">{{'ARE_YOU_SURE_WANT_TO_DELETE' | translate}}?</h1>
<p class="um-delete-subtitle">{{'THIS_ACTION_CANNOT_BE_UNDONE' | translate}}</p>
</div>
<div class="um-delete-details">
<div class="um-delete-detail-item">
<span class="um-delete-detail-label">{{'USER_ID' | translate}}:</span>
<span class="um-delete-detail-value">{{rowSelected.userId}}</span>
</div>
<div class="um-delete-detail-item">
<span class="um-delete-detail-label">{{'FULL_NAME' | translate}}:</span>
<span class="um-delete-detail-value">{{rowSelected.fullName}}</span>
</div>
<div class="um-delete-detail-item">
<span class="um-delete-detail-label">{{'EMAIL' | translate}}:</span>
<span class="um-delete-detail-value">{{rowSelected.email}}</span>
</div>
</div>
<h1 class="delete">Are You Sure Want to delete?</h1>
<h2 class="heading">{{rowSelected.userId}}</h2>
<div class="modal-footer">
<button type="button" class="um-btn um-btn-outline" (click)="modaldelete = false">{{'CANCEL' |
translate}}</button>
<button type="submit" (click)="delete(rowSelected.userId)" class="um-btn um-btn-error">{{'DELETE' |
translate}}</button>
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
<button type="submit" (click)="delete(rowSelected.userId)" class="btn btn-primary" >Delete</button>
</div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
<h3 class="modal-title">{{'IMPORT_FILE' | translate}}</h3>
<h3 class="modal-title">Import File</h3>
<div class="modal-body">
<div class="um-form-group">
<label class="um-form-label">{{'SELECT_CSV_FILE' | translate}}</label>
<input type="file" name="file" class="um-file-input" (change)="selectFile($event)"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
</div>
<input type="file" name="file" class="file" (change)="selectFile($event)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
</div>
<div class="modal-footer">
<button class="um-btn um-btn-outline" type="button" (click)="modalCsv = false">{{'CANCEL' | translate}}</button>
<button class="um-btn um-btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">{{'IMPORT' |
translate}}</button>
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
</div>
</clr-modal>

View File

@@ -1,656 +1,4 @@
@import '../../../../../styles.scss';
// Import design tokens
@import '../../../../../styles/design-tokens';
.delete, .heading {
.delete,.heading{
text-align: center;
color: red;
}
// Modern User Maintenance Styles
.um-hero {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px;
margin-bottom: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
font-family: var(--theme-font-primary);
&__content {
display: flex;
align-items: center;
gap: 16px;
}
&__icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
clr-icon {
width: 36px;
height: 36px;
color: white;
}
}
&__title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: white;
font-family: var(--theme-font-secondary);
}
&__subtitle {
font-size: 16px;
margin: 0;
opacity: 0.9;
color: rgba(255, 255, 255, 0.8);
}
&__actions {
display: flex;
gap: 12px;
}
}
.um-btn-text {
margin-left: 8px;
}
// Modern Button Styles using ThemeService
.um-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
line-height: 1;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
transition: all 200ms ease-out;
text-decoration: none;
position: relative;
overflow: hidden;
font-family: var(--theme-font-primary);
z-index: 1;
&:focus {
outline: 2px solid var(--theme-primary);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
// Sizes
&.um-btn-sm {
padding: 8px 16px;
font-size: 13px;
height: 32px;
}
&.um-btn-md {
padding: 12px 20px;
font-size: 14px;
height: 40px;
}
&.um-btn-lg {
padding: 16px 24px;
font-size: 16px;
height: 48px;
}
// Variants
&.um-btn-primary {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-color: var(--theme-primary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: linear-gradient(135deg, var(--theme-primary, #0284c7) 0%, var(--theme-accent, #7c3aed) 100%);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
&:active {
transform: translateY(0);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
}
&.um-btn-secondary {
background: var(--theme-surface);
color: var(--theme-text);
border-color: rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-background);
border-color: rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.um-btn-outline {
background: transparent;
color: var(--theme-secondary);
border-color: var(--theme-secondary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: rgba(100, 116, 139, 0.1); // var(--theme-secondary) with 10% opacity
border-color: var(--theme-secondary);
color: var(--theme-secondary);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.um-btn-error {
background: var(--theme-error, #ef4444);
color: white;
border-color: var(--theme-error, #ef4444);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-error-dark, #dc2626);
border-color: var(--theme-error-dark, #dc2626);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.3), 0 4px 6px -2px rgba(239, 68, 68, 0.1);
}
}
&.um-btn-ghost {
background: transparent;
color: var(--theme-text-secondary);
border-color: transparent;
&:hover {
background: var(--theme-background);
color: var(--theme-text);
}
}
}
.um-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 16px;
background: var(--theme-surface);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&__left {
flex: 1;
}
&__right {
display: flex;
gap: 8px;
}
}
.um-search {
position: relative;
max-width: 400px;
clr-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
z-index: 1;
}
&__input {
width: 100%;
padding: 12px 12px 12px 40px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
font-size: 14px;
transition: all 200ms ease-out;
background: var(--theme-surface);
color: var(--theme-text);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1); // var(--theme-primary) with 10% opacity
}
}
}
.um-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 8px;
overflow: hidden;
.um-btn {
border-radius: 0;
border: none;
background: var(--theme-surface);
color: var(--theme-text-secondary);
&:first-child {
border-right: 1px solid #d1d5db;
}
&.um-btn-primary {
background: var(--theme-primary);
color: white;
}
}
}
// Modern Table Styles
.um-grid {
background: var(--theme-surface);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
::ng-deep .datagrid {
.datagrid-head {
background: var(--theme-background);
.datagrid-column {
padding: 16px 24px;
font-size: 12px;
font-weight: 600;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e5e7eb;
}
}
.datagrid-row {
transition: background-color 150ms ease-out;
&:hover {
background: var(--theme-background);
}
&:not(:last-child) {
border-bottom: 1px solid #e5e7eb;
}
.datagrid-cell {
padding: 16px 24px;
font-size: 14px;
color: var(--theme-text);
}
}
.datagrid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
padding: 16px 24px;
}
}
}
.um-grid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
}
// Modern Card Styles - More beautiful design
.um-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
margin-bottom: 24px;
}
.um-card-item {
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--theme-border-radius); // Using theme variable
box-shadow: var(--theme-shadow); // Using theme variable
overflow: hidden;
display: flex;
flex-direction: column;
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-5px);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
border-color: rgba(14, 165, 233, 0.3); // var(--theme-primary) with 30% opacity
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 16px;
background: linear-gradient(135deg, rgba(14, 165, 233, 0.1), transparent); // var(--theme-primary) with 10% opacity
min-height: 60px;
}
&__icon {
clr-icon {
width: 24px;
height: 24px;
color: var(--theme-primary);
}
}
&__title {
font-weight: 700;
flex: 1 1 auto;
font-size: 18px;
color: var(--theme-text);
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__badge {
margin-left: auto;
display: inline-flex;
align-items: center;
padding: 4px 12px;
font-size: 12px;
font-weight: 600;
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.05em;
background: linear-gradient(135deg, rgba(100, 116, 139, 0.1) 0%, rgba(100, 116, 139, 0.05) 100%);
color: var(--theme-secondary);
}
&__body {
padding: 16px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
&__footer {
padding: 16px;
display: flex;
gap: 8px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
background: var(--theme-background);
}
}
.um-kv {
display: flex;
justify-content: space-between;
font-size: 14px;
span {
color: var(--theme-text-secondary);
}
strong {
color: var(--theme-text);
font-weight: 500;
text-align: right;
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
}
}
// Theme the card footer buttons
.um-card-item__footer .um-btn {
border-radius: 10px;
flex: 1;
justify-content: center;
}
.um-card-item__footer .um-btn.um-btn-outline {
border: 1px solid var(--theme-secondary);
color: var(--theme-secondary);
background: transparent;
}
.um-card-item__footer .um-btn.um-btn-outline:hover {
background: rgba(0, 0, 0, 0.04);
}
.um-card-item__footer .um-btn.um-btn-error {
background: var(--theme-error, #ef4444);
color: #fff;
}
.um-card-item__footer .um-btn.um-btn-error:hover {
background: var(--theme-error-dark, #dc2626);
}
// Action items in table
.um-action-item {
@extend .um-btn;
@extend .um-btn-ghost;
@extend .um-btn-sm;
width: 100%;
justify-content: flex-start;
margin-bottom: 4px;
text-align: left;
clr-icon {
width: 16px;
height: 16px;
margin-right: 8px;
}
&.um-btn-error {
color: var(--theme-error, #ef4444);
&:hover {
background: rgba(239, 68, 68, 0.1);
}
}
}
// Ensure action overflow is visible
::ng-deep .datagrid-action-overflow {
button.action-item {
display: flex;
align-items: center;
width: 100%;
text-align: left;
padding: 8px 12px;
clr-icon {
margin-right: 8px;
}
}
}
// Delete Modal Styles
.um-delete-header {
text-align: center;
padding: 24px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.um-delete-icon {
color: #ef4444;
margin-bottom: 16px;
}
.um-delete-title {
font-size: 24px;
font-weight: 700;
color: var(--theme-text);
margin: 0 0 8px 0;
}
.um-delete-subtitle {
font-size: 16px;
color: var(--theme-text-secondary);
margin: 0;
}
.um-delete-details {
padding: 24px;
}
.um-delete-detail-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:last-child {
border-bottom: none;
}
}
.um-delete-detail-label {
font-weight: 500;
color: var(--theme-text-secondary);
}
.um-delete-detail-value {
font-weight: 600;
color: var(--theme-text);
}
// Form Styles
.um-form-group {
margin-bottom: 20px;
}
.um-form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--theme-text);
margin-bottom: 8px;
font-family: var(--theme-font-primary);
}
.um-file-input {
width: 100%;
padding: 12px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
background: var(--theme-surface);
color: var(--theme-text);
&::file-selector-button {
@extend .um-btn;
@extend .um-btn-secondary;
margin-right: 12px;
padding: 8px 16px;
border: none;
}
}
// Modal Header
::ng-deep .modal-header {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px 16px 0 0 !important;
padding: 20px 24px !important;
.modal-title {
color: white;
font-weight: 600;
font-size: 20px;
margin: 0;
}
.close {
color: white;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
// Modal Body
::ng-deep .modal-body {
padding: 24px !important;
}
// Modal Footer
::ng-deep .modal-footer {
padding: 20px 24px !important;
background: var(--theme-background);
border-radius: 0 0 16px 16px !important;
border-top: 1px solid rgba(0, 0, 0, 0.05) !important;
}
// Responsive adjustments
@media (max-width: 768px) {
.um-hero {
flex-direction: column;
gap: 16px;
text-align: center;
&__content {
flex-direction: column;
text-align: center;
}
&__actions {
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
}
.um-toolbar {
flex-direction: column;
gap: 16px;
}
.um-search {
max-width: 100%;
}
.um-cards {
grid-template-columns: 1fr;
}
.um-card-item {
&__header {
flex-direction: column;
text-align: center;
&__icon {
margin-right: 0;
margin-bottom: 16px;
}
}
&__footer {
justify-content: center;
}
}
}

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ExcelService } from '../../../../services/excel.service';
@@ -8,15 +8,12 @@ import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
import { ToastrService } from 'ngx-toastr';
import { CsvService } from 'src/app/services/csv.service';
import { TranslateService } from '@ngx-translate/core';
import { ThemeService } from 'src/app/services/theme.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-usermaintance',
templateUrl: './usermaintance.component.html',
styleUrls: ['./usermaintance.component.scss']
})
export class UsermaintanceComponent implements OnInit, OnDestroy {
export class UsermaintanceComponent implements OnInit {
loading = false;
loading1=false;
public entryForm: FormGroup;
@@ -24,18 +21,13 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
orders;
modalAdd= false;
modaledit=false;
mcreate: string | boolean = false;
medit: string | boolean = false;
mdelete;
showdata;
error;
modaldelete=false;
rowSelected :any= {};
// UI-only state
filterText: string = '';
viewMode: 'table' | 'cards' = 'cards';
private themeSubscription: Subscription;
mcreate;
medit;
mdelete;
showdata;
error;
modaldelete=false;
rowSelected :any= {};
constructor(private excel: ExcelService,
private _fb: FormBuilder,
private router: Router, private toastr:ToastrService,
@@ -44,41 +36,25 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
private mainservice:UsermaintanceService,
private csvService: CsvService,
private translate: TranslateService,
private themeService: ThemeService,
) {this.loading1 = true;
setTimeout(() => {
this.loading1 = false;
}, 1000); }
switchLanguage(language: string) {
this.translate.use(language);
}
ngOnInit(): void {
this.themeSubscription = this.themeService.currentTheme$.subscribe(() => {
// Theme changes will automatically update CSS variables
// This triggers a re-render of themed elements
});
this.showdata = this.menuGroupService.getdata();
console.log(this.showdata);
if (this.showdata) {
this.mcreate = this.showdata.mcreate === 'true' || this.showdata.mcreate === true ? true : false;
this.mcreate=this.showdata.mcreate;
console.log(this.mcreate);
this.mdelete=this.showdata.mdelete
console.log(this.mdelete);
this.medit = this.showdata.medit === 'true' || this.showdata.medit === true ? true : false;
this.medit=this.showdata.medit
console.log(this.medit);
}
this.getData();
}
ngOnDestroy(): void {
if (this.themeSubscription) {
this.themeSubscription.unsubscribe();
}
}
getData(){
this.mainservice.getAll().subscribe((data) => {
console.log(data);
@@ -103,23 +79,6 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
});
}
// UI helpers
get totalUsers(): number {
const list: any[] = (this.givendata as unknown as any[]) || [];
return list.length;
}
get filteredUsers(): any[] {
const items: any[] = (this.givendata as unknown as any[]) || [];
const text = (this.filterText || '').toLowerCase();
if (!text) { return items; }
return items.filter(u => (
(u?.fullName || '').toLowerCase().includes(text) ||
(u?.email || '').toLowerCase().includes(text) ||
(u?.usrGrpName || '').toLowerCase().includes(text)
));
}
setViewMode(mode: 'table' | 'cards') { this.viewMode = mode; }
// csv
@@ -170,7 +129,7 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
this.router.navigate(["../usermaintancedit/"+ id], { relativeTo: this.route });
}
onDelete(row) {
this.rowSelected = {...row}; // Create a copy to avoid reference issues
this.rowSelected = row;
this.modaldelete=true;
}

View File

@@ -1,169 +1,204 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon> Security</a></li>
<li><a href="javascript://">User Maintenance</a></li>
</ol>
<section class="um-hero">
<div class="um-hero__content">
<div class="um-hero__icon"><clr-icon shape="user"></clr-icon></div>
<div>
<h2 class="um-hero__title">User Maintenance</h2>
<p class="um-hero__subtitle">Add New User</p>
</div>
</div>
</section>
<h4 style="font-weight: 300;display: inline;"> User Maintenance</h4>
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Add Mode</span><br>
<hr>
<div class="main" >
<form [formGroup]="entryForm">
<div class="um-add-container">
<div class="um-form-card">
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()" class="um-form">
<div class="clr-row">
<div class="clr-col-md-6 clr-col-sm-12">
<label for="first_name" class="um-form-label">First Name<span class="required-field">*</span></label>
<input type="text" id="first_name" class="um-form-input" formControlName="first_name">
<div *ngIf="submitted && entryForm.controls.first_name.errors" class="um-error-message">
<div *ngIf="submitted && entryForm.controls.first_name.errors.required">*This field is Required</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name"> First Name</label>
<input type="text" class="clr-input" formControlName="first_name">
<div *ngIf="submitted && entryForm.controls.first_name.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.first_name.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Last Name</label>
<input type="text" class="clr-input" formControlName="last_name">
<div *ngIf="submitted && entryForm.controls.last_name.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.last_name.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Mobile Number</label>
<input type="text" class="clr-input" formControlName="mob_no" required pattern="((\+)?([1-9]{2}))?[1-9]{1}[0-9]{9}$">
<div *ngIf="submitted && entryForm.controls.mob_no.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.mob_no.errors.required" class="error_mess">*This field is Required</div>
<div *ngIf="submitted && entryForm.controls.mob_no.errors.pattern" class="error_mess">* Please Follow your pattern,+91 Enter 10 digit Mobile Number.
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="last_name" class="um-form-label">Last Name<span class="required-field">*</span></label>
<input type="text" id="last_name" class="um-form-input" formControlName="last_name">
<div *ngIf="submitted && entryForm.controls.last_name.errors" class="um-error-message">
<div *ngIf="submitted && entryForm.controls.last_name.errors.required">*This field is Required</div>
</div>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">New Password<span class="required-field">*</span></label>
<input type="password" class="clr-input" formControlName="new_password" minlenght="6">
<div *ngIf="submitted && entryForm.controls.new_password.errors" class="error_mess">
<div *ngIf="entryForm.controls.new_password.errors.required" class="error_mess">*Password is required</div>
<div *ngIf="entryForm.controls.new_password.errors.minlength" class="error_mess">* Password must be at least 6 characters</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="mob_no" class="um-form-label">Mobile Number<span class="required-field">*</span></label>
<input type="text" id="mob_no" class="um-form-input" formControlName="mob_no" required pattern="((\+)?([1-9]{2}))?[1-9]{1}[0-9]{9}$">
<div *ngIf="submitted && entryForm.controls.mob_no.errors" class="um-error-message">
<div *ngIf="submitted && entryForm.controls.mob_no.errors.required">*This field is Required</div>
<div *ngIf="submitted && entryForm.controls.mob_no.errors.pattern">* Please Follow your pattern,+91 Enter 10 digit Mobile Number.</div>
</div>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Confirm New Password<span class="required-field">*</span></label>
<input type="password" class="clr-input" formControlName="confirm_password">
<div *ngIf="submitted && entryForm.controls.confirm_password.errors" class="error_mess">
<div *ngIf="entryForm.controls.confirm_password.errors.required" class="error_mess">* Confirm Password is required</div>
<div *ngIf="entryForm.controls.confirm_password.errors.confirmedValidator" class="error_mess">* Password and Confirm Password must be match.</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="email" class="um-form-label">Email<span class="required-field">*</span></label>
<input type="text" id="email" class="um-form-input" formControlName="email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$">
<div *ngIf="submitted && entryForm.controls.email.errors" class="um-error-message">
<div *ngIf="submitted && entryForm.controls.email.errors.required">*This field is Required</div>
<div *ngIf="submitted && entryForm.controls.email.errors.pattern">*Email must be a valid email address</div>
</div>
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="new_password" class="um-form-label">New Password<span class="required-field">*</span></label>
<input type="password" id="new_password" class="um-form-input" formControlName="new_password" minlength="6">
<div *ngIf="submitted && entryForm.controls.new_password.errors" class="um-error-message">
<div *ngIf="entryForm.controls.new_password.errors.required">*Password is required</div>
<div *ngIf="entryForm.controls.new_password.errors.minlength">* Password must be at least 6 characters</div>
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Business Title</label>
<input type="text" class="clr-input" formControlName="title">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name"> Short Name</label>
<input type="text" class="clr-input" formControlName="shortName">
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="confirm_password" class="um-form-label">Confirm New Password<span class="required-field">*</span></label>
<input type="password" id="confirm_password" class="um-form-input" formControlName="confirm_password">
<div *ngIf="submitted && entryForm.controls.confirm_password.errors" class="um-error-message">
<div *ngIf="entryForm.controls.confirm_password.errors.required">* Confirm Password is required</div>
<div *ngIf="entryForm.controls.confirm_password.errors.confirmedValidator">* Password and Confirm Password must be match.</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Full Name</label>
<input type="text" class="clr-input" formControlName="fullName">
</div> -->
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Status</label>
<select id="" formControlName="status">
<option value="E">Enable</option>
<option value="A">Active</option></select>
</div> -->
<!-- <div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Position Code</label>
<input type="text" class="clr-input" formControlName="positionCodeString" [(ngModel)]="userobjpos.positionCode" name="positionCode" >
</div>
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotoposition()">V</button>
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="usrGrpId" class="um-form-label">User Group<span class="required-field">*</span></label>
<select id="usrGrpId" class="um-form-select" formControlName="usrGrpId">
<option value="">Select User Group</option>
<option *ngFor="let group of usergrpdata" [value]="group.usrGrp">{{group.groupName}}</option>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Department Code</label>
<input type="text" class="clr-input" formControlName="departmentCodeString" [(ngModel)]="userobjdept.departmentCode" name="departmentCode">
</div>
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotodepartmet()">V</button>
</div> -->
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">User Group</label>
<select id="tags" formControlName="usrGrpId" >
<option *ngFor="let sub of usergrpdata" [value]="sub.usrGrp">{{sub.groupName}}</option>
</select>
<div *ngIf="submitted && entryForm.controls.usrGrpId.errors" class="um-error-message">
<div *ngIf="submitted && entryForm.controls.usrGrpId.errors.required">*This field is Required</div>
<div *ngIf="submitted && entryForm.controls.usrGrpId.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.usrGrpId.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Customer Id</label>
<input type="text" class="clr-input" formControlName="customerId" name="customerCode">
</div> -->
<!-- <div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" >V</button>
</div> -->
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Email</label>
<input type="text" class="clr-input" formControlName="email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$">
<div *ngIf="submitted && entryForm.controls.email.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.email.errors.required" class="error_mess">*This field is Required</div>
<!-- <div *ngIf="submitted && entryForm.controls.email.errors.email" class="error_mess">*Email must be a valid email address</div> -->
<div *ngIf="submitted && entryForm.controls.email.errors.pattern" class="error_mess">*Email must be a valid email address</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="accesstype" class="um-form-label">Access Type<span class="required-field">*</span></label>
<select id="accesstype" class="um-form-select" formControlName="accesstype">
<option value="">Select Access Type</option>
<option *ngFor="let access of accessdata" [value]="access.name">{{access.name}}</option>
</div>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Access Type</label>
<select name="usrGrpId" formControlName="accesstype">
<option *ngFor="let sub of accessdata" [value]="sub.name" [selected]="true">{{sub.name}}</option>
</select>
<div *ngIf="submitted && entryForm.controls.accesstype.errors" class="um-error-message">
<div *ngIf="submitted && entryForm.controls.accesstype.errors.required">*This field is Required</div>
</div>
<div *ngIf="submitted && entryForm.controls.accesstype.errors" class="error_mess">
<div *ngIf="submitted && entryForm.controls.accesstype.errors.required" class="error_mess">*This field is Required</div>
</div>
</div>
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Notification</label>
<select id="" formControlName="notification">
<option value="N" >NO</option>
<option value="Y">YES</option></select>
</div> -->
<div class="um-form-actions">
<button type="button" class="um-btn um-btn-outline" (click)="goback()">
<clr-icon shape="times"></clr-icon>
<span>Cancel</span>
</button>
<button type="submit" class="um-btn um-btn-primary">
<clr-icon shape="check"></clr-icon>
<span>Add User</span>
</button>
</div>
<!-- <div class="clr-row">
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Created By</label>
<input type="text" class="clr-input">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Created On</label>
<input type="text" class="clr-input">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Updated By</label>
<input type="text" class="clr-input">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Updated On</label>
<input type="text" class="clr-input">
</div>
</div> -->
</form>
</div>
</div>
<!-- Customer Modal -->
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="goback()">Cancel</button>
<button type="button" class="btn btn-primary" (click)="onSubmit()">Add</button>
</div>
</div>
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
<h3 class="modal-title"><b>Select From List:</b></h3>
<div class="modal-body">
<div class="clr-row">
<div class="clr-col-md-12 clr-col-sm-12">
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder>
<clr-spinner>Loading ...</clr-spinner>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'">
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
No
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Customer Code
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerCode'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerCode'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Customer Id
</ng-container>
</clr-dg-column>
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerName'">
<ng-container *clrDgHideableColumn="{hidden: false}">
Customer Name
</ng-container>
</clr-dg-column>
</clr-dg-column >
<clr-dg-row *clrDgItems="let user of custdata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell (click)="getcustID(user.customerId)" class="um-clickable-cell">{{user.customerCode}}</clr-dg-cell>
<clr-dg-cell>{{user.customerId}}</clr-dg-cell>
<clr-dg-cell (click)="getcustID(user.customerId)" style="color: rgb(108, 108, 194);" >{{user.customerCode}}</clr-dg-cell>
<clr-dg-cell >{{user.customerId}}</clr-dg-cell>
<clr-dg-cell>{{user.customerName}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">No</td>
<td class="td-title">NO</td>
<td class="td-content">{{user.id}}</td>
</tr>
<tr>
<td class="td-title">Customer Code</td>
<td class="td-title"> Customer Code</td>
<td class="td-content">{{user.customerCode}}</td>
</tr>
<tr>
<td class="td-title">Customer Id</td>
<td class="td-content">{{user.customerId}}</td>
<td class="td-content">{{user.customerId}} </td>
</tr>
<tr>
<td class="td-title">Customer Name</td>
<td class="td-title"> Customer Name<td>
<td class="td-content">{{user.customerName}}</td>
</tr>
</table>
@@ -179,41 +214,38 @@
</clr-dg-footer>
</clr-datagrid>
</div>
</clr-modal>
</clr-modal>
<!-- Department Modal -->
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
<h3 class="modal-title"><b>Select From List:</b></h3>
<div class="modal-body">
<div class="clr-row">
<div class="clr-col-md-12 clr-col-sm-12">
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder>
<clr-spinner>Loading ...</clr-spinner>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'">
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
No
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Department Code
</ng-container>
</clr-dg-column>
</ng-container></clr-dg-column>
<clr-dg-row *clrDgItems="let user of departmentdata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell (click)="getdepid(user.departmentCode)" class="um-clickable-cell">{{user.departmentCode}}</clr-dg-cell>
<clr-dg-cell (click)="getdepid(user.departmentCode)" style="color: rgb(108, 108, 194);" >{{user.departmentCode}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">No</td>
<td class="td-title">NO</td>
<td class="td-content">{{user.id}}</td>
</tr>
</table>
@@ -228,42 +260,43 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</clr-modal>
<!-- Position Modal -->
<clr-modal [(clrModalOpen)]="position" [clrModalSize]="'lg'">
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="position" [clrModalSize]="'lg'">
<h3 class="modal-title"><b>Select From List:</b></h3>
<div class="modal-body">
<div class="clr-row">
<div class="clr-col-md-12 clr-col-sm-12">
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder>
<clr-spinner>Loading ...</clr-spinner>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'">
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
No
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Position Code
</ng-container>
</clr-dg-column>
</ng-container></clr-dg-column>
<clr-dg-row *clrDgItems="let user of positiondata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell (click)="getposid(user.positionCode)" class="um-clickable-cell">{{user.positionCode}}</clr-dg-cell>
<clr-dg-cell (click)="getposid(user.positionCode)" style="color: rgb(108, 108, 194);" >{{user.positionCode}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">No</td>
<td class="td-title">NO</td>
<td class="td-content">{{user.id}}</td>
</tr>
</table>
@@ -278,5 +311,9 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</clr-modal>
</clr-modal>

View File

@@ -1,6 +1,4 @@
@import '../../../../../styles1.scss';
@import '../../../../../styles/_design-tokens.scss';
input.ng-invalid.ng-touched {
border-color: red;
}
@@ -8,383 +6,3 @@ input.ng-invalid.ng-touched {
.error_mess {
color: red;
}
// User Maintenance Add Styles
.um-hero {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px;
margin-bottom: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
font-family: var(--theme-font-primary);
&__content {
display: flex;
align-items: center;
gap: 16px;
}
&__icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
clr-icon {
width: 36px;
height: 36px;
color: white;
}
}
&__title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: white;
font-family: var(--theme-font-secondary);
}
&__subtitle {
font-size: 16px;
margin: 0;
opacity: 0.9;
color: rgba(255, 255, 255, 0.8);
}
}
.um-add-container {
background: var(--theme-surface);
border-radius: 16px;
padding: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
}
.um-form-card {
background: var(--theme-surface);
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.um-form {
.clr-row {
margin: 0 -12px;
}
.clr-col-md-6,
.clr-col-sm-12 {
padding: 0 12px;
margin-bottom: 20px;
}
}
.um-form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--theme-text);
margin-bottom: 8px;
font-family: var(--theme-font-primary);
}
.um-form-input,
.um-form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
line-height: 1.5;
color: var(--theme-text);
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
transition: all 200ms ease-out;
margin-bottom: 0;
font-family: var(--theme-font-primary);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1); // var(--theme-primary) with 10% opacity
}
&:disabled {
background: var(--theme-background);
color: var(--theme-text-secondary);
cursor: not-allowed;
}
&.error {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
&::placeholder {
color: var(--theme-text-secondary);
}
}
.um-form-select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 12px center;
background-repeat: no-repeat;
background-size: 16px 12px;
padding-right: 40px;
appearance: none;
}
.um-error-message {
color: #ef4444;
font-size: 12px;
margin-top: 6px;
font-weight: 500;
}
.required-field {
color: #ef4444;
margin-left: 4px;
}
.um-form-actions {
display: flex;
gap: 12px;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
// Modern Button Styles using ThemeService
.um-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
line-height: 1;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
transition: all 200ms ease-out;
text-decoration: none;
position: relative;
overflow: hidden;
font-family: var(--theme-font-primary);
z-index: 1;
&:focus {
outline: 2px solid var(--theme-primary);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
// Sizes
&.um-btn-sm {
padding: 8px 16px;
font-size: 13px;
height: 32px;
}
&.um-btn-md {
padding: 12px 20px;
font-size: 14px;
height: 40px;
}
&.um-btn-lg {
padding: 16px 24px;
font-size: 16px;
height: 48px;
}
// Variants
&.um-btn-primary {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-color: var(--theme-primary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: linear-gradient(135deg, var(--theme-primary, #0284c7) 0%, var(--theme-accent, #7c3aed) 100%);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
&:active {
transform: translateY(0);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
}
&.um-btn-outline {
background: transparent;
color: var(--theme-secondary);
border-color: var(--theme-secondary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: rgba(100, 116, 139, 0.1); // var(--theme-secondary) with 10% opacity
border-color: var(--theme-secondary);
color: var(--theme-secondary);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.um-btn-error {
background: var(--theme-error, #ef4444);
color: white;
border-color: var(--theme-error, #ef4444);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-error-dark, #dc2626);
border-color: var(--theme-error-dark, #dc2626);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.3), 0 4px 6px -2px rgba(239, 68, 68, 0.1);
}
}
&.um-btn-ghost {
background: transparent;
color: var(--theme-text-secondary);
border-color: transparent;
&:hover {
background: var(--theme-background);
color: var(--theme-text);
}
}
}
.um-clickable-cell {
color: var(--theme-primary);
cursor: pointer;
transition: all 200ms ease-out;
&:hover {
text-decoration: underline;
transform: translateY(-1px);
}
}
// Modal Styles
::ng-deep .modal-header {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px 16px 0 0 !important;
padding: 20px 24px !important;
.modal-title {
color: white;
font-weight: 600;
font-size: 20px;
margin: 0;
}
.close {
color: white;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
::ng-deep .modal-body {
padding: 24px !important;
}
::ng-deep .modal-footer {
padding: 20px 24px !important;
background: var(--theme-background);
border-radius: 0 0 16px 16px !important;
border-top: 1px solid rgba(0, 0, 0, 0.05) !important;
}
// Data Grid Styles
.um-datagrid {
background: var(--theme-surface);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
::ng-deep .datagrid {
.datagrid-head {
background: var(--theme-background);
.datagrid-column {
padding: 16px 24px;
font-size: 12px;
font-weight: 600;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e5e7eb;
}
}
.datagrid-row {
transition: background-color 150ms ease-out;
&:hover {
background: var(--theme-background);
}
&:not(:last-child) {
border-bottom: 1px solid #e5e7eb;
}
.datagrid-cell {
padding: 16px 24px;
font-size: 14px;
color: var(--theme-text);
}
}
.datagrid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
padding: 16px 24px;
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.um-hero {
flex-direction: column;
gap: 16px;
text-align: center;
&__content {
flex-direction: column;
text-align: center;
}
}
.um-form-actions {
flex-direction: column;
}
.um-btn {
width: 100%;
}
}

View File

@@ -4,9 +4,6 @@ import { ActivatedRoute, Router } from '@angular/router';
import { AccesstypeService } from 'src/app/services/admin/accesstype.service';
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
import { TranslateService } from '@ngx-translate/core';
import { ThemeService } from 'src/app/services/theme.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-usermaintanceadd',
@@ -15,62 +12,50 @@ import { Subscription } from 'rxjs';
})
export class UsermaintanceaddComponent implements OnInit {
public entryForm: FormGroup;
customer: boolean = false;
department: boolean = false;
position: boolean = false;
customer:boolean=false;
department:boolean=false;
position:boolean=false;
custdata: any;
loading = false;
clickedID: number;
clickedID:number;
custiddata: any;
userobjcust = {
customerName: '',
customerCode: '',
userobjcust={
customerName:'',
customerCode:'',
}
departmentdata: any;
positiondata: any;
deptiddata: any;
userobjdept = {
departmentCode: '',
userobjdept={
departmentCode:'',
}
userobjpos = {
positionCode: '',
userobjpos={
positionCode:'',
}
posiddata: any;
usergrpdata;
accessdata;
error;
submitted = false;
private themeSubscription: Subscription;
submitted=false;
constructor(private _fb: FormBuilder,
private mainservice: UsermaintanceService,
private router: Router, private accesstype: AccesstypeService,
constructor( private _fb: FormBuilder,
private mainservice:UsermaintanceService,
private router: Router,private accesstype:AccesstypeService,
private route: ActivatedRoute,
private usergrpservice: UsergrpmaintainceService,
private translate: TranslateService,
private themeService: ThemeService,
private usergrpservice: UsergrpmaintainceService
) { }
switchLanguage(language: string) {
this.translate.use(language);
}
ngOnInit(): void {
this.themeSubscription = this.themeService.currentTheme$.subscribe(() => {
// Theme changes will automatically update CSS variables
// This triggers a re-render of themed elements
});
this.entryForm = this._fb.group({
first_name: ['', [Validators.required]],
last_name: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]],
mob_no: ['', [Validators.required]],
new_password: ['', [Validators.required, Validators.minLength(6)]],
confirm_password: ['', [Validators.required]],
usrGrpId: ['', [Validators.required]],
account_id: 1,
accesstype: ['', [Validators.required]],
first_name :['',[Validators.required]],
last_name:['',[Validators.required]],
email:['',[Validators.required,Validators.email]],
mob_no:['',[Validators.required]],
new_password:['',[Validators.required,Validators.minLength(6)]],
confirm_password:['',[Validators.required]],
usrGrpId:['',[Validators.required]],
account_id:1,
accesstype:['',[Validators.required]],
// status:[null],
// username:[null] ,
// userPassw:[null] ,
@@ -96,29 +81,22 @@ export class UsermaintanceaddComponent implements OnInit {
this.usergrp();
this.getdata();
}
ngOnDestroy(): void {
if (this.themeSubscription) {
this.themeSubscription.unsubscribe();
}
}
getdata() {
getdata(){
this.accesstype.getAll().subscribe(resp => {
this.accessdata = resp;
console.log('accessdata: ', this.accessdata);
if (this.accessdata.length == 0) {
this.error = "No data Available";
if(this.accessdata.length==0){
this.error="No data Available";
console.log(this.error)
}
}, (error) => {
},(error) => {
console.log(error);
if (error) {
this.error = "Server Error";
if(error){
this.error="Server Error";
}
})
}
usergrp() {
usergrp(){
this.usergrpservice.getAll().subscribe((data) => {
console.log(data);
this.usergrpdata = data;
@@ -142,8 +120,8 @@ export class UsermaintanceaddComponent implements OnInit {
// });
//}
onSubmit() {
this.submitted = true
onSubmit(){
this.submitted=true
if (this.entryForm.invalid) {
return;
}
@@ -158,53 +136,53 @@ export class UsermaintanceaddComponent implements OnInit {
this.router.navigate(["../usermaintance"], { relativeTo: this.route });
}
goback() {
goback(){
this.router.navigate(["../usermaintance"], { relativeTo: this.route });
}
gotodepartmet() {
this.department = !this.department;
this.mainservice.getalldepartment().subscribe((data) => {
}
gotodepartmet(){
this.department=!this.department;
this.mainservice.getalldepartment().subscribe((data)=>{
console.log(data);
this.departmentdata = data;
this.departmentdata=data;
});
}
getdepid(id: number) {
this.clickedID = id;
console.log("clicked by id" + id);
getdepid(id:number){
this.clickedID=id;
console.log("clicked by id"+ id);
this.mainservice.getbydepartmentid(id).subscribe((data) => {
console.log(data);
this.deptiddata = data;
this.deptiddata= data;
// this.userObj= this.custiddata;
this.userobjdept = this.deptiddata;
this.userobjdept =this.deptiddata;
});
this.department = false;
this.department=false;
}
gotoposition() {
this.position = !this.position;
this.mainservice.getallposition().subscribe((data) => {
gotoposition(){
this.position=!this.position;
this.mainservice.getallposition().subscribe((data)=>{
console.log(data);
this.positiondata = data;
this.positiondata=data;
})
}
getposid(id: number) {
this.clickedID = id;
console.log("clicked by id" + id);
getposid(id:number){
this.clickedID=id;
console.log("clicked by id"+ id);
this.mainservice.getbypositionid(id).subscribe((data) => {
console.log(data);
this.posiddata = data;
this.posiddata= data;
// this.userObj= this.custiddata;
this.userobjpos = this.posiddata;
this.userobjpos =this.posiddata;
});
this.position = false;
this.position=false;
}
}
export function ConfirmedValidator(controlName: string, matchingControlName: string) {
export function ConfirmedValidator(controlName: string, matchingControlName: string){
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];
const matchingControl = formGroup.controls[matchingControlName];

View File

@@ -1,136 +1,234 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong mm-breadcrumb">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="lock"></clr-icon> Security</a></li>
<li><a href="javascript://">User Maintenance</a></li>
</ol>
<section class="um-hero">
<div class="um-hero__content">
<div class="um-hero__icon"><clr-icon shape="user"></clr-icon></div>
<div>
<h2 class="um-hero__title">User Maintenance</h2>
<p class="um-hero__subtitle">Edit User</p>
</div>
</div>
</section>
<div class="um-edit-container">
<div class="um-form-card">
<form *ngIf="data1" (ngSubmit)="update()" class="um-form">
<h4 style="font-weight: 300;display: inline;"> User Maintenance</h4>
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Edit Mode</span><br>
<hr>
<div class="main" >
<form *ngIf="data1">
<div class="clr-row">
<div class="clr-col-md-6 clr-col-sm-12">
<label for="first_name" class="um-form-label">First Name<span class="required-field">*</span></label>
<input type="text" id="first_name" class="um-form-input" name="first_name" [(ngModel)]="data1.fullName">
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name"> First Name</label>
<input type="text" class="clr-input" name="first_name" [(ngModel)]="data1.fullName" >
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Last Name</label>
<input type="text" class="clr-input" name="last_name" [(ngModel)]="data1.last_name">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Mobile Number</label>
<input type="text" class="clr-input" name="mob_no" [(ngModel)]="data1.mob_no">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">New Password<span class="required-field">*</span></label>
<input type="password" class="clr-input" name="new_password" [(ngModel)]="data1.userPassw">
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="last_name" class="um-form-label">Last Name<span class="required-field">*</span></label>
<input type="text" id="last_name" class="um-form-input" name="last_name" [(ngModel)]="data1.last_name">
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Business Title</label>
<input type="text" class="clr-input" formControlName="title">
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="mob_no" class="um-form-label">Mobile Number<span class="required-field">*</span></label>
<input type="text" id="mob_no" class="um-form-input" name="mob_no" [(ngModel)]="data1.mob_no">
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name"> Short Name</label>
<input type="text" class="clr-input" formControlName="shortName">
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="email" class="um-form-label">Email<span class="required-field">*</span></label>
<input type="text" id="email" class="um-form-input" name="email" [(ngModel)]="data1.email">
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Full Name</label>
<input type="text" class="clr-input" formControlName="fullName">
</div> -->
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Status</label>
<select id="" formControlName="status">
<option value="E">Enable</option>
<option value="A">Active</option></select>
</div> -->
<!-- <div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Position Code</label>
<input type="text" class="clr-input" formControlName="positionCodeString" [(ngModel)]="userobjpos.positionCode" name="positionCode" >
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="new_password" class="um-form-label">New Password<span class="required-field">*</span></label>
<input type="password" id="new_password" class="um-form-input" name="new_password" [(ngModel)]="data1.userPassw">
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotoposition()">V</button>
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="usrGrpId" class="um-form-label">User Group<span class="required-field">*</span></label>
<select id="usrGrpId" class="um-form-select" [(ngModel)]="data1.usrGrpId" name="usrGrpId">
<option value="">Select User Group</option>
<option *ngFor="let group of usergrpdata" [value]="group.usrGrp">{{group.groupName}}</option>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Department Code</label>
<input type="text" class="clr-input" formControlName="departmentCodeString" [(ngModel)]="userobjdept.departmentCode" name="departmentCode">
</div>
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotodepartmet()">V</button>
</div> -->
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">User Group</label>
<select [(ngModel)]="data1.usrGrpId" name="usrGrpId">
<option *ngFor="let sub of usergrpdata" [value]="sub.usrGrp">{{sub.groupName}}</option>
</select>
</div>
<div class="clr-col-md-6 clr-col-sm-12">
<label for="accesstype" class="um-form-label">Access Type<span class="required-field">*</span></label>
<select id="accesstype" class="um-form-select" [(ngModel)]="data1.accesstype" name="accesstype">
<option value="">Select Access Type</option>
<option *ngFor="let access of accessdata" [value]="access.name">{{access.name}}</option>
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Customer Id</label>
<input type="text" class="clr-input" formControlName="customerId" name="customerCode">
</div> -->
<!-- <div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" >V</button>
</div> -->
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Email</label>
<input type="text" class="clr-input" name="email" [(ngModel)]="data1.email">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Access Type</label>
<select [(ngModel)]="data1.accesstype" name="usrGrpId">
<option *ngFor="let sub of accessdata" [value]="sub.name">{{sub.name}}</option>
</select>
</div>
<!-- <div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Notification</label>
<select id="" formControlName="notification">
<option value="N" >NO</option>
<option value="Y">YES</option></select>
</div> -->
</div>
<!-- <div class="clr-row">
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name"> User Name</label>
<input type="text" class="clr-input" [(ngModel)]="data1.username" name="username">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">New Password<span class="required-field">*</span></label>
<input type="password" class="clr-input" [(ngModel)]="data1.userPassw" name="userPassw">
</div>
<div class="um-form-actions">
<button type="button" class="um-btn um-btn-outline" (click)="goback()">
<clr-icon shape="times"></clr-icon>
<span>Close</span>
</button>
<button type="submit" class="um-btn um-btn-primary">
<clr-icon shape="check"></clr-icon>
<span>Update User</span>
</button>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Business Title</label>
<input type="text" class="clr-input" [(ngModel)]="data1.title" name="title">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name"> Short Name</label>
<input type="text" class="clr-input" [(ngModel)]="data1.shortName" name="shortName">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Full Name</label>
<input type="text" class="clr-input" [(ngModel)]="data1.fullName" name="fullName">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Status</label>
<select [(ngModel)]="data1.status" name="status">
<option value="E">Enable</option>
<option value="A">Active</option></select>
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Position Code</label>
<input type="text" class="clr-input" [(ngModel)]="data1.positionCodeString" [value]="userobjpos.positionCode" name="positionCodeString">
</div>
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotoposition()">V</button>
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Department Code</label>
<input type="text" class="clr-input" [(ngModel)]="data1.departmentCodeString" [value]="userobjdept.departmentCode" name="departmentCodeString">
</div>
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotodepartmet()">V</button>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">User Group</label>
<select [(ngModel)]="data1.usrGrpId" name="usrGrpId">
<option *ngFor="let sub of usergrpdata" [value]="sub.usrGrp">{{sub.groupName}}</option>
</select>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Customer Id</label>
<input type="text" class="clr-input" [(ngModel)]="data1.customerId" [value]="userobjcust.customerCode" name="customerId">
</div>
<div class="clr-col-md-1 clr-col-sm-12" style="margin-top: 25px;">
<button style="margin-right: 5px;" (click)="gotocustomerid()">V</button>
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="name">Email</label>
<input type="text" class="clr-input"[(ngModel)]="data1.email" name="email">
</div>
<div class="clr-col-md-4 clr-col-sm-12">
<label for="tags">Notification</label>
<select [(ngModel)]="data1.notification" name="notification">
<option value="N" >NO</option>
<option value="Y">YES</option></select>
</div>
</div>
<div class="clr-row">
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Created By</label>
<input type="text" class="clr-input" [(ngModel)]="data1.createby" name="createby">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Created On</label>
<input type="text" class="clr-input" [(ngModel)]="data1.createdate" name="createdate">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Updated By</label>
<input type="text" class="clr-input" [(ngModel)]="data1.updateby" name="updateby">
</div>
<div class="clr-col-md-3 clr-col-sm-12">
<label for="name">Updated On</label>
<input type="text" class="clr-input"[(ngModel)]="data1.updatedate" name="updatedate">
</div>
</div> -->
</form>
</div>
</div>
<!-- Customer Modal -->
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="goback()">Close</button>
<button type="button" class="btn btn-primary" (click)="update()">Update</button>
</div>
</div>
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
<h3 class="modal-title"><b>Select From List:</b></h3>
<div class="modal-body">
<div class="clr-row">
<div class="clr-col-md-12 clr-col-sm-12">
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder>
<clr-spinner>Loading ...</clr-spinner>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'">
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
No
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Customer Code
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerCode'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerCode'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Customer Id
</ng-container>
</clr-dg-column>
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerName'">
<ng-container *clrDgHideableColumn="{hidden: false}">
Customer Name
</ng-container>
</clr-dg-column>
</clr-dg-column >
<clr-dg-row *clrDgItems="let user of custdata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell (click)="getcustID(user.customerId)" class="um-clickable-cell">{{user.customerCode}}</clr-dg-cell>
<clr-dg-cell>{{user.customerId}}</clr-dg-cell>
<clr-dg-cell (click)="getcustID(user.customerId)" style="color: rgb(108, 108, 194);" >{{user.customerCode}}</clr-dg-cell>
<clr-dg-cell >{{user.customerId}}</clr-dg-cell>
<clr-dg-cell>{{user.customerName}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">No</td>
<td class="td-title">NO</td>
<td class="td-content">{{user.id}}</td>
</tr>
<tr>
<td class="td-title">Customer Code</td>
<td class="td-title"> Customer Code</td>
<td class="td-content">{{user.customerCode}}</td>
</tr>
<tr>
<td class="td-title">Customer Id</td>
<td class="td-content">{{user.customerId}}</td>
<td class="td-content">{{user.customerId}} </td>
</tr>
<tr>
<td class="td-title">Customer Name</td>
<td class="td-title"> Customer Name<td>
<td class="td-content">{{user.customerName}}</td>
</tr>
</table>
@@ -146,41 +244,38 @@
</clr-dg-footer>
</clr-datagrid>
</div>
</clr-modal>
</clr-modal>
<!-- Department Modal -->
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
<h3 class="modal-title"><b>Select From List:</b></h3>
<div class="modal-body">
<div class="clr-row">
<div class="clr-col-md-12 clr-col-sm-12">
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder>
<clr-spinner>Loading ...</clr-spinner>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'">
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
No
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Department Code
</ng-container>
</clr-dg-column>
</ng-container></clr-dg-column>
<clr-dg-row *clrDgItems="let user of departmentdata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell (click)="getdepid(user.departmentCode)" class="um-clickable-cell">{{user.departmentCode}}</clr-dg-cell>
<clr-dg-cell (click)="getdepid(user.departmentCode)" style="color: rgb(108, 108, 194);" >{{user.departmentCode}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">No</td>
<td class="td-title">NO</td>
<td class="td-content">{{user.id}}</td>
</tr>
</table>
@@ -195,42 +290,42 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</clr-modal>
<!-- Position Modal -->
<clr-modal [(clrModalOpen)]="position" [clrModalSize]="'lg'">
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="position" [clrModalSize]="'lg'">
<h3 class="modal-title"><b>Select From List:</b></h3>
<div class="modal-body">
<div class="clr-row">
<div class="clr-col-md-12 clr-col-sm-12">
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder>
<clr-spinner>Loading ...</clr-spinner>
</clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'">
<ng-container *clrDgHideableColumn="{hidden: false}">
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
No
</ng-container>
</clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'">
<ng-container *clrDgHideableColumn="{hidden: false}">
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Position Code
</ng-container>
</clr-dg-column>
</ng-container></clr-dg-column>
<clr-dg-row *clrDgItems="let user of positiondata;let i = index" [clrDgItem]="user">
<clr-dg-cell>{{i+1}}</clr-dg-cell>
<clr-dg-cell (click)="getposid(user.positionCode)" class="um-clickable-cell">{{user.positionCode}}</clr-dg-cell>
<clr-dg-cell (click)="getposid(user.positionCode)" style="color: rgb(108, 108, 194);" >{{user.positionCode}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<table class="table">
<tr>
<td class="td-title">No</td>
<td class="td-title">NO</td>
<td class="td-content">{{user.id}}</td>
</tr>
</table>
@@ -245,5 +340,9 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</clr-modal>
</clr-modal>

View File

@@ -1,375 +1 @@
@import '../../../../../styles1.scss';
@import '../../../../../styles/_design-tokens.scss';
// User Maintenance Edit Styles
.um-hero {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px;
margin-bottom: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
font-family: var(--theme-font-primary);
&__content {
display: flex;
align-items: center;
gap: 16px;
}
&__icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
clr-icon {
width: 36px;
height: 36px;
color: white;
}
}
&__title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: white;
font-family: var(--theme-font-secondary);
}
&__subtitle {
font-size: 16px;
margin: 0;
opacity: 0.9;
color: rgba(255, 255, 255, 0.8);
}
}
.um-edit-container {
background: var(--theme-surface);
border-radius: 16px;
padding: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
}
.um-form-card {
background: var(--theme-surface);
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.um-form {
.clr-row {
margin: 0 -12px;
}
.clr-col-md-6,
.clr-col-sm-12 {
padding: 0 12px;
margin-bottom: 20px;
}
}
.um-form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--theme-text);
margin-bottom: 8px;
font-family: var(--theme-font-primary);
}
.um-form-input,
.um-form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
line-height: 1.5;
color: var(--theme-text);
background: var(--theme-surface);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
transition: all 200ms ease-out;
margin-bottom: 0;
font-family: var(--theme-font-primary);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1); // var(--theme-primary) with 10% opacity
}
&:disabled {
background: var(--theme-background);
color: var(--theme-text-secondary);
cursor: not-allowed;
}
&.error {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
&::placeholder {
color: var(--theme-text-secondary);
}
}
.um-form-select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 12px center;
background-repeat: no-repeat;
background-size: 16px 12px;
padding-right: 40px;
appearance: none;
}
.required-field {
color: #ef4444;
margin-left: 4px;
}
.um-form-actions {
display: flex;
gap: 12px;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
// Modern Button Styles using ThemeService
.um-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
line-height: 1;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
transition: all 200ms ease-out;
text-decoration: none;
position: relative;
overflow: hidden;
font-family: var(--theme-font-primary);
z-index: 1;
&:focus {
outline: 2px solid var(--theme-primary);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
// Sizes
&.um-btn-sm {
padding: 8px 16px;
font-size: 13px;
height: 32px;
}
&.um-btn-md {
padding: 12px 20px;
font-size: 14px;
height: 40px;
}
&.um-btn-lg {
padding: 16px 24px;
font-size: 16px;
height: 48px;
}
// Variants
&.um-btn-primary {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-color: var(--theme-primary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: linear-gradient(135deg, var(--theme-primary, #0284c7) 0%, var(--theme-accent, #7c3aed) 100%);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
&:active {
transform: translateY(0);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
}
&.um-btn-outline {
background: transparent;
color: var(--theme-secondary);
border-color: var(--theme-secondary);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: rgba(100, 116, 139, 0.1); // var(--theme-secondary) with 10% opacity
border-color: var(--theme-secondary);
color: var(--theme-secondary);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
}
&.um-btn-error {
background: var(--theme-error, #ef4444);
color: white;
border-color: var(--theme-error, #ef4444);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
&:hover {
background: var(--theme-error-dark, #dc2626);
border-color: var(--theme-error-dark, #dc2626);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.3), 0 4px 6px -2px rgba(239, 68, 68, 0.1);
}
}
&.um-btn-ghost {
background: transparent;
color: var(--theme-text-secondary);
border-color: transparent;
&:hover {
background: var(--theme-background);
color: var(--theme-text);
}
}
}
.um-clickable-cell {
color: var(--theme-primary);
cursor: pointer;
transition: all 200ms ease-out;
&:hover {
text-decoration: underline;
transform: translateY(-1px);
}
}
// Modal Styles
::ng-deep .modal-header {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-accent) 100%);
color: white;
border-radius: 16px 16px 0 0 !important;
padding: 20px 24px !important;
.modal-title {
color: white;
font-weight: 600;
font-size: 20px;
margin: 0;
}
.close {
color: white;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
::ng-deep .modal-body {
padding: 24px !important;
}
::ng-deep .modal-footer {
padding: 20px 24px !important;
background: var(--theme-background);
border-radius: 0 0 16px 16px !important;
border-top: 1px solid rgba(0, 0, 0, 0.05) !important;
}
// Data Grid Styles
.um-datagrid {
background: var(--theme-surface);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
::ng-deep .datagrid {
.datagrid-head {
background: var(--theme-background);
.datagrid-column {
padding: 16px 24px;
font-size: 12px;
font-weight: 600;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e5e7eb;
}
}
.datagrid-row {
transition: background-color 150ms ease-out;
&:hover {
background: var(--theme-background);
}
&:not(:last-child) {
border-bottom: 1px solid #e5e7eb;
}
.datagrid-cell {
padding: 16px 24px;
font-size: 14px;
color: var(--theme-text);
}
}
.datagrid-footer {
background: var(--theme-background);
border-top: 1px solid #e5e7eb;
padding: 16px 24px;
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.um-hero {
flex-direction: column;
gap: 16px;
text-align: center;
&__content {
flex-direction: column;
text-align: center;
}
}
.um-form-actions {
flex-direction: column;
}
.um-btn {
width: 100%;
}
}

View File

@@ -3,9 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { AccesstypeService } from 'src/app/services/admin/accesstype.service';
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
import { TranslateService } from '@ngx-translate/core';
import { ThemeService } from 'src/app/services/theme.service';
import { Subscription } from 'rxjs';
//import { Usermain } from 'src/app/models/usermaintaince';
@Component({
selector: 'app-usermaintanceedit',
@@ -13,52 +11,40 @@ import { Subscription } from 'rxjs';
styleUrls: ['./usermaintanceedit.component.scss']
})
export class UsermaintanceeditComponent implements OnInit {
id: number;
data1: any = {};
customer: boolean = false;
id:number;
data1:any={};
customer:boolean=false;
custdata: any;
clickedID: number;
clickedID:number;
custiddata: any;
loading = false;
userobjcust = {
customerName: '',
customerCode: '',
userobjcust={
customerName:'',
customerCode:'',
}
accessdata;
department: boolean = false;
department:boolean=false;
departmentdata: any;
positiondata: any;
deptiddata: any;
userobjdept = {
departmentCode: '',
userobjdept={
departmentCode:'',
}
userobjpos = {
positionCode: '',
userobjpos={
positionCode:'',
}
posiddata: any;
position: boolean = false;
position:boolean=false;
usergrpdata;
error;
private themeSubscription: Subscription;
constructor(private route:ActivatedRoute,
private mainservice:UsermaintanceService,
private router: Router,private accesstype:AccesstypeService,
private usergrpservice: UsergrpmaintainceService
constructor(private route: ActivatedRoute,
private mainservice: UsermaintanceService,
private router: Router, private accesstype: AccesstypeService,
private usergrpservice: UsergrpmaintainceService,
private translate: TranslateService,
private themeService: ThemeService,
) { }
switchLanguage(language: string) {
this.translate.use(language);
}
ngOnInit(): void {
this.themeSubscription = this.themeService.currentTheme$.subscribe(() => {
// Theme changes will automatically update CSS variables
// This triggers a re-render of themed elements
});
this.id = this.route.snapshot.params["id"];
console.log("update with id = ", this.id);
this.getById(this.id);
@@ -66,89 +52,82 @@ export class UsermaintanceeditComponent implements OnInit {
this.usergrp();
this.getdata();
}
ngOnDestroy(): void {
if (this.themeSubscription) {
this.themeSubscription.unsubscribe();
}
}
getdata() {
getdata(){
this.accesstype.getAll().subscribe(resp => {
this.accessdata = resp;
console.log('accessdata: ', this.accessdata);
if (this.accessdata.length == 0) {
this.error = "No data Available";
if(this.accessdata.length==0){
this.error="No data Available";
console.log(this.error)
}
}, (error) => {
},(error) => {
console.log(error);
if (error) {
this.error = "Server Error";
if(error){
this.error="Server Error";
}
})
}
usergrp() {
usergrp(){
this.usergrpservice.getAll().subscribe((data) => {
console.log(data);
this.usergrpdata = data;
});
}
getById(id: number) {
this.mainservice.getbyid(id).subscribe((data) => {
this.data1 = data;
getById(id:number){
this.mainservice.getbyid(id).subscribe((data)=>{
this.data1=data;
// this.data1=this.data3;
console.log(this.data1);
});
}
update() {
});
}
update(){
console.log(this.data1);
this.mainservice.updatenew(this.id, this.data1).subscribe((data) => {
console.log(data);
this.mainservice.updatenew(this.id,this.data1).subscribe((data)=>{
console.log(data);
});
}
goback() {
}
goback(){
this.router.navigate(["../../usermaintance"], { relativeTo: this.route });
}
gotodepartmet() {
this.department = !this.department;
this.mainservice.getalldepartment().subscribe((data) => {
}
gotodepartmet(){
this.department=!this.department;
this.mainservice.getalldepartment().subscribe((data)=>{
console.log(data);
this.departmentdata = data;
this.departmentdata=data;
});
}
getdepid(id: number) {
this.clickedID = id;
console.log("clicked by id" + id);
getdepid(id:number){
this.clickedID=id;
console.log("clicked by id"+ id);
this.mainservice.getbydepartmentid(id).subscribe((data) => {
console.log(data);
this.deptiddata = data;
this.deptiddata= data;
// this.userObj= this.custiddata;
this.userobjdept = this.deptiddata;
this.userobjdept =this.deptiddata;
});
this.department = false;
this.department=false;
}
gotoposition() {
this.position = !this.position;
this.mainservice.getallposition().subscribe((data) => {
gotoposition(){
this.position=!this.position;
this.mainservice.getallposition().subscribe((data)=>{
console.log(data);
this.positiondata = data;
this.positiondata=data;
})
}
getposid(id: number) {
this.clickedID = id;
console.log("clicked by id" + id);
getposid(id:number){
this.clickedID=id;
console.log("clicked by id"+ id);
this.mainservice.getbypositionid(id).subscribe((data) => {
console.log(data);
this.posiddata = data;
this.posiddata= data;
// this.userObj= this.custiddata;
this.userobjpos = this.posiddata;
this.userobjpos =this.posiddata;
});
this.position = false;
this.position=false;
}
}

View File

@@ -1,877 +0,0 @@
/* Calculated Fields Modal Styles */
.calculated-section {
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
background-color: #f9f9f9;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.section-title {
color: #0072a0;
font-weight: 600;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.section-icon {
margin-right: 10px;
}
.field-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.field-input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
.field-input:focus {
border-color: #0072a0;
outline: none;
box-shadow: 0 0 0 2px rgba(0, 114, 160, 0.2);
}
.field-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.field-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;
}
.field-tag:hover {
background-color: #bbdefb;
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.field-icon {
margin-right: 5px;
color: #1976d2;
}
.equation-input {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 6px;
font-family: monospace;
font-size: 14px;
margin-bottom: 8px;
}
.equation-hint {
display: block;
margin-bottom: 15px;
color: #666;
font-style: italic;
}
.operators-container,
.fields-container,
.constants-container,
.new-constant-container {
margin-bottom: 20px;
}
.subsection-title {
color: #555;
font-weight: 500;
margin-bottom: 10px;
display: flex;
align-items: center;
}
.subsection-icon {
margin-right: 8px;
color: #0072a0;
}
.operator-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.operator-tag {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0072a0;
color: white;
border: 1px solid #005a80;
border-radius: 4px;
padding: 6px 10px;
font-size: 14px;
font-family: monospace;
cursor: pointer;
transition: all 0.2s;
min-width: 36px;
}
.operator-tag:hover {
background-color: #005a80;
transform: scale(1.1);
}
.field-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.constant-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.constant-tag {
display: inline-flex;
align-items: center;
background-color: #4caf50;
color: white;
border: 1px solid #388e3c;
border-radius: 20px;
padding: 6px 12px;
margin: 4px;
font-size: 13px;
font-family: monospace;
cursor: pointer;
transition: all 0.2s;
}
.constant-tag:hover {
background-color: #388e3c;
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.constant-icon {
margin-right: 5px;
}
.add-constant-btn {
background-color: #ff9800;
border-color: #f57c00;
}
.add-constant-btn:hover {
background-color: #f57c00;
}
.field-components-container {
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 15px;
}
.field-component-row {
display: flex;
align-items: center;
margin-bottom: 10px;
gap: 10px;
}
.field-select-container,
.constant-input-container {
flex: 1;
}
.field-select,
.constant-input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.remove-field-btn {
padding: 6px;
}
.add-field-btn {
margin-top: 10px;
background-color: #1976d2;
border-color: #1976d2;
color: white;
}
.add-field-btn:hover {
background-color: #1565c0;
}
.created-fields-section {
margin-top: 25px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.table-container {
overflow-x: auto;
}
.calculated-fields-table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
border-radius: 6px;
overflow: hidden;
}
.calculated-fields-table th {
background-color: #f5f5f5;
padding: 12px 15px;
text-align: left;
font-weight: 600;
color: #333;
}
.calculated-fields-table td {
padding: 12px 15px;
border-bottom: 1px solid #eee;
}
.field-name-cell {
font-weight: 500;
color: #0072a0;
}
.operation-cell {
font-family: monospace;
background-color: #e3f2fd;
border-radius: 4px;
padding: 4px 8px;
display: inline-block;
}
.expression-cell {
font-family: monospace;
font-size: 13px;
}
.actions-cell {
text-align: center;
}
.delete-field-btn {
padding: 6px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.field-component-row {
flex-direction: column;
align-items: stretch;
}
.field-select-container,
.constant-input-container {
margin-bottom: 8px;
}
}
/* Group By Modal Styles */
.field-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.field-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;
}
.field-tag:hover {
background-color: #bbdefb;
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.field-tag.selected {
background-color: #1976d2;
color: white;
border-color: #1976d2;
}
.selected-fields-container {
min-height: 50px;
padding: 10px;
border: 1px dashed #ccc;
border-radius: 4px;
background-color: #f9f9f9;
}
.selected-field-tag {
display: inline-flex;
align-items: center;
background-color: #1976d2;
color: white;
border-radius: 20px;
padding: 6px 12px;
margin: 4px;
font-size: 13px;
}
.selected-field-tag .btn-icon {
margin-left: 8px;
padding: 2px;
color: white;
}
.no-selection {
color: #999;
font-style: italic;
padding: 10px;
}
.aggregation-container {
margin-top: 10px;
}
.aggregation-row {
background-color: #f5f5f5;
padding: 15px;
border-radius: 4px;
margin-bottom: 10px;
}
.aggregation-row label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.clr-select {
width: 100%;
padding: 5px;
border: 1px solid #ccc;
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;
}

View File

@@ -1,67 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable } from "rxjs";
import { HttpClient, HttpHeaders, HttpParams, } from "@angular/common/http";
import { ApiRequestService } from "src/app/services/api/api-request.service";
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class Data_lakeservice{
private baseURL = "Data_lake/Data_lake" ; constructor(
private http: HttpClient,
private apiRequest: ApiRequestService,
) { }
getAll(page?: number, size?: number): Observable<any> {
return this.apiRequest.get(this.baseURL);
}
getById(id: number): Observable<any> {
const _http = this.baseURL + "/" + id;
return this.apiRequest.get(_http);
}
create(data: any): Observable<any> {
return this.apiRequest.post(this.baseURL, data);
}
update(id: number, data: any): Observable<any> {
const _http = this.baseURL + "/" + id;
return this.apiRequest.put(_http, data);
}
delete(id: number): Observable<any> {
const _http = this.baseURL + "/" + id;
return this.apiRequest.delete(_http);
}
// New method for updating JSON
updateJson(id: number): Observable<any> {
const _http = this.baseURL + "/json/" + id;
return this.apiRequest.put(_http, {});
}
// Method to fetch available keys from API
fetchAvailableKeys(url: string, sureId: number): Observable<string[]> {
const apiUrl = `chart/getAllKeys?apiUrl=${encodeURIComponent(url)}&sureId=${sureId}`;
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
updateCalculatedFields(id: number, calculatedFieldJson: string, isCalculatedField: boolean): Observable<any> {
const _http = this.baseURL + "/" + id;
const data = {
calculated_field_json: calculatedFieldJson,
iscalculatedfield: isCalculatedField
};
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
}

View File

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

View File

@@ -1,75 +0,0 @@
<div class="cron-job-builder">
<h4>Cron Job Builder</h4>
<div class="clr-row">
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
<div class="form-group">
<label>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="form-group">
<label>Minute</label>
<select class="form-control" [(ngModel)]="minute" (change)="onFieldChange()">
<option *ngFor="let option of minuteOptions" [value]="option.value">{{ option.label }}</option>
</select>
</div>
</div>
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
<div class="form-group">
<label>Hour</label>
<select class="form-control" [(ngModel)]="hour" (change)="onFieldChange()">
<option *ngFor="let option of hourOptions" [value]="option.value">{{ option.label }}</option>
</select>
</div>
</div>
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
<div class="form-group">
<label>Day of Month</label>
<select class="form-control" [(ngModel)]="dayOfMonth" (change)="onFieldChange()">
<option *ngFor="let option of dayOfMonthOptions" [value]="option.value">{{ option.label }}</option>
</select>
</div>
</div>
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
<div class="form-group">
<label>Month</label>
<select class="form-control" [(ngModel)]="month" (change)="onFieldChange()">
<option *ngFor="let option of monthOptions" [value]="option.value">{{ option.label }}</option>
</select>
</div>
</div>
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
<div class="form-group">
<label>Day of Week</label>
<select class="form-control" [(ngModel)]="dayOfWeek" (change)="onFieldChange()">
<option *ngFor="let option of dayOfWeekOptions" [value]="option.value">{{ option.label }}</option>
</select>
</div>
</div>
</div>
<div class="clr-row">
<div class="clr-col-12">
<div class="form-group">
<label>Generated Cron Expression:</label>
<input type="text" class="form-control" [value]="buildCronExpression()" readonly>
</div>
</div>
</div>
<div class="clr-row" *ngIf="cronDescription">
<div class="clr-col-12">
<div class="cron-description">
<strong>Schedule Description:</strong> {{ cronDescription }}
</div>
</div>
</div>
</div>

View File

@@ -1,39 +0,0 @@
.cron-job-builder {
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f8f8f8;
margin-bottom: 20px;
h4 {
margin-top: 0;
color: #333;
}
.form-group {
margin-bottom: 10px;
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
select, input {
width: 100%;
}
}
.cron-description {
padding: 10px;
background-color: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
margin-top: 10px;
font-size: 14px;
strong {
color: #1890ff;
}
}
}

View File

@@ -1,200 +0,0 @@
import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-cron-job-builder',
templateUrl: './cron-job-builder.component.html',
styleUrls: ['./cron-job-builder.component.scss']
})
export class CronJobBuilderComponent implements OnInit, OnChanges {
@Input() cronExpression: string = '';
@Input() instanceId: string = ''; // Unique identifier for each instance
@Output() cronExpressionChange = new EventEmitter<string>();
// Cron job fields (now starting with seconds)
second: string = '*';
minute: string = '*';
hour: string = '*';
dayOfMonth: string = '*';
month: string = '*';
dayOfWeek: string = '*';
// Human readable description
cronDescription: string = '';
// Options for selectors
secondOptions: any[] = [];
minuteOptions: any[] = [];
hourOptions: any[] = [];
dayOfMonthOptions: any[] = [];
monthOptions: any[] = [];
dayOfWeekOptions: any[] = [];
ngOnInit() {
// Initialize options for each instance
this.secondOptions = this.generateOptions(0, 59, 'second');
this.minuteOptions = this.generateOptions(0, 59, 'minute');
this.hourOptions = this.generateOptions(0, 23, 'hour');
this.dayOfMonthOptions = this.generateOptions(1, 31, 'day');
this.monthOptions = [
{ value: '*', label: 'Every month' },
{ value: '1', label: 'January' },
{ value: '2', label: 'February' },
{ value: '3', label: 'March' },
{ value: '4', label: 'April' },
{ value: '5', label: 'May' },
{ value: '6', label: 'June' },
{ value: '7', label: 'July' },
{ value: '8', label: 'August' },
{ value: '9', label: 'September' },
{ value: '10', label: 'October' },
{ value: '11', label: 'November' },
{ value: '12', label: 'December' }
];
this.dayOfWeekOptions = [
{ value: '*', label: 'Every day' },
{ value: '0', label: 'Sunday' },
{ value: '1', label: 'Monday' },
{ value: '2', label: 'Tuesday' },
{ value: '3', label: 'Wednesday' },
{ value: '4', label: 'Thursday' },
{ value: '5', label: 'Friday' },
{ value: '6', label: 'Saturday' }
];
if (this.cronExpression) {
this.parseCronExpression(this.cronExpression);
}
// Generate initial description
this.generateDescription();
}
ngOnChanges(changes: SimpleChanges) {
// When cronExpression input changes, update the component state
if (changes['cronExpression'] && !changes['cronExpression'].firstChange) {
this.parseCronExpression(changes['cronExpression'].currentValue);
this.generateDescription();
}
}
generateOptions(start: number, end: number, type: string): any[] {
const options = [{ value: '*', label: `Every ${type} (${start === 0 ? '0' : start}-${end})` }];
for (let i = start; i <= end; i++) {
options.push({ value: i.toString(), label: i.toString() });
}
return options;
}
parseCronExpression(expression: string) {
const parts = expression.split(' ');
if (parts.length >= 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.hour = parts[1];
this.dayOfMonth = parts[2];
this.month = parts[3];
this.dayOfWeek = parts[4];
}
}
generateDescription() {
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
if (this.minute === '*') {
if (this.second === '*') {
description += '';
} else {
description += ' of every minute';
}
} else if (this.minute.includes('/')) {
const interval = this.minute.split('/')[1];
description += ` of every ${interval} minutes`;
} else {
description += ` at minute ${this.minute}`;
}
// Hour description
if (this.hour === '*') {
if (this.minute === '*' && this.second === '*') {
description += '';
} else {
description += ' past every hour';
}
} else if (this.hour.includes('/')) {
const interval = this.hour.split('/')[1];
description += ` past every ${interval} hours`;
} else {
description += ` past hour ${this.hour}`;
}
// Day of month description
if (this.dayOfMonth !== '*' && this.dayOfMonth !== '?') {
description += ` on day ${this.dayOfMonth}`;
}
// Month description
if (this.month !== '*') {
const monthNames = ['', 'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
if (this.month.match(/^\d+$/)) {
description += ` in ${monthNames[parseInt(this.month)]}`;
} else {
description += ` in ${this.month}`;
}
}
// Day of week description
if (this.dayOfWeek !== '*' && this.dayOfWeek !== '?') {
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
if (this.dayOfWeek.match(/^\d+$/)) {
description += ` on ${dayNames[parseInt(this.dayOfWeek)]}`;
} else {
description += ` on ${this.dayOfWeek}`;
}
}
// Special case for simple patterns
if (this.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';
} else if (this.second === '0' && this.minute === '0' && this.hour === '0' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
description = 'Runs at midnight every day';
} else if (this.second === '0' && this.minute === '0' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
description = 'Runs at the top of every hour';
} else if (this.second === '0' && this.minute === '*' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
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;
}
buildCronExpression() {
const expression = `${this.second} ${this.minute} ${this.hour} ${this.dayOfMonth} ${this.month} ${this.dayOfWeek}`;
this.cronExpressionChange.emit(expression);
this.generateDescription(); // Update description when expression changes
return expression;
}
onFieldChange() {
this.buildCronExpression();
}
}

View File

@@ -1,47 +0,0 @@
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);
}
}

View File

@@ -1,26 +1,19 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="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-col-8">x
<div class="clr-col-8">
<h3>{{ 'Dashboard_builder' | translate }}</h3>
</div>
<div class="clr-col-4" style="text-align: right;">
<button class="btn btn-success" [routerLink]="['/cns-portal/shield-dashboard']">
<clr-icon shape="shield"></clr-icon>Shield Dashboard
</button>
<button id="add" class="btn btn-primary" (click)="gotorunner()">
<clr-icon shape="grid-view"></clr-icon>{{ 'Dashboard_runner' | translate }}
</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()">
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
</button>
@@ -31,10 +24,8 @@
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{ 'Loading' | translate }} ......
</clr-spinner></ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div>
</clr-dg-placeholder>
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{ 'Loading' | translate }} ...... </clr-spinner></ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
<clr-dg-column [clrDgField]="''">
<ng-container *clrDgHideableColumn="{hidden: false}">
@@ -50,17 +41,17 @@
<ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Description' | translate }}
</ng-container>
</clr-dg-column>
</clr-dg-column >
<clr-dg-column [clrDgField]="'secuirity_profile'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Security_Profile' | translate }}
</ng-container>
</clr-dg-column>
</clr-dg-column >
<clr-dg-column [clrDgField]="'add_to_home'">
<ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Add_to_home' | translate }}
</ng-container>
</clr-dg-column>
</clr-dg-column >
<!-- <clr-dg-column [clrDgField]="'formType'">
<ng-container *clrDgHideableColumn="{hidden: false}">
Build Status
@@ -76,8 +67,7 @@
</ng-container></clr-dg-column>
<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.description}}</clr-dg-cell>
<clr-dg-cell>{{user.secuirity_profile}}</clr-dg-cell>
@@ -87,16 +77,13 @@
<clr-dg-cell>{{user.testing}}</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> -->
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true"
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>
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true" 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>
</a>
<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>
<h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5>
<div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div>
@@ -109,8 +96,7 @@
</clr-dg-cell>
<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> -->
</clr-dg-action-overflow>
</clr-dg-row>
@@ -123,16 +109,16 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<clr-modal [(clrModalOpen)]="addModall" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<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>
<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>
@@ -142,15 +128,18 @@
</div>
</div>
</div>
</clr-modal>
</clr-modal>
<clr-modal [(clrModalOpen)]="modalDelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<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>
<button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary" >{{ 'Delete' | translate }}</button>
</div>
</div>
</clr-modal>
</clr-modal>

View File

@@ -24,7 +24,6 @@ export class AllnewdashComponent implements OnInit {
projectname;
projectId;
error;
chartConfigManagerOpen = false;
constructor(
private router : Router,
private route: ActivatedRoute,private dashboardService : DashboardService,
@@ -122,9 +121,4 @@ export class AllnewdashComponent implements OnInit {
// 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,630 +0,0 @@
<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>

View File

@@ -1,60 +0,0 @@
.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;
}
}

View File

@@ -1,658 +0,0 @@
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;
}
}

View File

@@ -1,495 +0,0 @@
<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>

View File

@@ -1,97 +0,0 @@
.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;
}

View File

@@ -1,710 +0,0 @@
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;
}
}

View File

@@ -1,46 +0,0 @@
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);
}
}

View File

@@ -1,42 +0,0 @@
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);
}
}

View File

@@ -1,125 +0,0 @@
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;
})
);
}
}

View File

@@ -1,46 +0,0 @@
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);
}
}

View File

@@ -1,33 +0,0 @@
<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>

View File

@@ -1,17 +0,0 @@
.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;
}

View File

@@ -1,22 +0,0 @@
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();
}
}

View File

@@ -1,33 +0,0 @@
<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>

View File

@@ -1,12 +0,0 @@
.chart-type-form {
padding: 20px;
background-color: #f9f9f9;
border-radius: 4px;
margin-bottom: 20px;
}
.form-actions {
margin-top: 20px;
display: flex;
gap: 10px;
}

View File

@@ -1,22 +0,0 @@
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();
}
}

View File

@@ -1,28 +0,0 @@
<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>

View File

@@ -1,12 +0,0 @@
.component-property-form {
padding: 20px;
background-color: #f9f9f9;
border-radius: 4px;
margin-bottom: 20px;
}
.form-actions {
margin-top: 20px;
display: flex;
gap: 10px;
}

View File

@@ -1,31 +0,0 @@
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();
}
}

View File

@@ -1,54 +0,0 @@
<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>

View File

@@ -1,17 +0,0 @@
.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;
}

View File

@@ -1,34 +0,0 @@
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();
}
}

View File

@@ -1,46 +0,0 @@
<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>

View File

@@ -1,12 +0,0 @@
.ui-component-form {
padding: 20px;
background-color: #f9f9f9;
border-radius: 4px;
margin-bottom: 20px;
}
.form-actions {
margin-top: 20px;
display: flex;
gap: 10px;
}

View File

@@ -1,33 +0,0 @@
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();
}
}

View File

@@ -1,47 +0,0 @@
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);
}
}

View File

@@ -1,76 +0,0 @@
<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

@@ -1,65 +0,0 @@
.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

@@ -1,70 +0,0 @@
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

@@ -1,222 +0,0 @@
<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>

View File

@@ -1,128 +0,0 @@
.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;
}
}
}
}
}

View File

@@ -1,181 +0,0 @@
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']);
}
}
}

View File

@@ -1,75 +0,0 @@
<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>

View File

@@ -1,71 +0,0 @@
.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;
}
}
}
}
}

View File

@@ -1,115 +0,0 @@
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;
}
});
}
}

View File

@@ -1,22 +0,0 @@
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 { }

View File

@@ -1,175 +0,0 @@
<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>

View File

@@ -1,128 +0,0 @@
.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;
}
}
}
}
}

View File

@@ -1,176 +0,0 @@
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']);
}
}
}

View File

@@ -1,312 +0,0 @@
<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>

View File

@@ -1,156 +0,0 @@
.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;
}
}
}
}
}

View File

@@ -1,309 +0,0 @@
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']);
}
}
}

View File

@@ -1,55 +0,0 @@
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);
}
}

View File

@@ -1,160 +0,0 @@
<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>

View File

@@ -1,163 +0,0 @@
.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

@@ -1,159 +0,0 @@
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;
}
});
}
}
}

View File

@@ -1,687 +0,0 @@
# Common Filter System - Example Usage
## Dashboard Layout
```
+-----------------------------------------------------+
| Common Filter Widget (Draggable) |
| |
| [Category ▼] [Status ▼] [Date Range] [Active -toggle]|
| [Save Preset] [Preset Selector] [Reset] |
+-----------------------------------------------------+
| +-----------+ +-----------+ +-----------+ |
| | Bar Chart | | Line Chart| | Pie Chart | |
| | | | | | | |
| | | | | | | |
| +-----------+ +-----------+ +-----------+ |
| |
| +-----------+ +-----------+ +-----------+ |
| | Table | | Map | | KPI Cards | |
| | | | | | | |
| | | | | | | |
| +-----------+ +-----------+ +-----------+ |
+-----------------------------------------------------+
```
## Filter Configuration Example
### 1. Creating Filter Definitions
```typescript
// In CommonFilterComponent or dashboard initialization
const filters: Filter[] = [
{
id: 'category',
field: 'product_category',
label: 'Category',
type: 'dropdown',
options: ['Electronics', 'Clothing', 'Home & Garden', 'Books', 'Sports']
},
{
id: 'status',
field: 'order_status',
label: 'Status',
type: 'multiselect',
options: ['Pending', 'Processing', 'Shipped', 'Delivered', 'Cancelled']
},
{
id: 'date_range',
field: 'order_date',
label: 'Order Date',
type: 'date-range'
},
{
id: 'active',
field: 'is_active',
label: 'Active Orders',
type: 'toggle'
}
];
// Set filters in the service
filterService.setFilters(filters);
```
### 2. Setting Initial Filter Values
```typescript
// Set initial values
filterService.updateFilterValue('category', 'Electronics');
filterService.updateFilterValue('status', ['Processing', 'Shipped']);
filterService.updateFilterValue('date_range', {
start: '2023-01-01',
end: '2023-12-31'
});
filterService.updateFilterValue('active', true);
```
### 3. Chart Component Integration
```typescript
// In bar-chart.component.ts
export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
private subscriptions: Subscription[] = [];
constructor(
private dashboardService: Dashboard3Service,
private filterService: FilterService
) { }
ngOnInit(): void {
// Subscribe to filter changes
this.subscriptions.push(
this.filterService.filterState$.subscribe(filters => {
this.refreshData();
})
);
// Load initial data
this.fetchChartData();
}
fetchChartData(): void {
// Get current filter values
const filterValues = this.filterService.getFilterValues();
// Build filter parameters for API
let filterParams = '';
if (Object.keys(filterValues).length > 0) {
const filterObj = {};
// Add base filters first
if (this.baseFilters && this.baseFilters.length > 0) {
this.baseFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
}
// Add common filters
Object.keys(filterValues).forEach(key => {
const value = filterValues[key];
if (value !== undefined && value !== null && value !== '') {
filterObj[key] = value;
}
});
if (Object.keys(filterObj).length > 0) {
filterParams = JSON.stringify(filterObj);
}
}
// Make API call with filters
this.dashboardService.getChartData(
this.table,
'bar',
this.xAxis,
this.yAxis,
this.connection,
'',
'',
filterParams
).subscribe(data => {
// Handle response
this.processChartData(data);
});
}
refreshData(): void {
this.fetchChartData();
}
ngOnDestroy(): void {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
}
```
### 4. API Endpoint Example
```javascript
// Backend API endpoint example
app.get('/chart/getdashjson/bar', (req, res) => {
const {
tableName,
xAxis,
yAxes,
sureId,
filters // JSON string of filters
} = req.query;
// Parse filters
let filterObj = {};
if (filters) {
try {
filterObj = JSON.parse(filters);
} catch (e) {
console.error('Failed to parse filters:', e);
}
}
// Build database query with filters
let query = `SELECT ${xAxis}, ${yAxes} FROM ${tableName}`;
const whereConditions = [];
// Add filter conditions
Object.keys(filterObj).forEach(field => {
const value = filterObj[field];
if (Array.isArray(value)) {
// Handle multiselect (IN clause)
const values = value.map(v => `'${v}'`).join(',');
whereConditions.push(`${field} IN (${values})`);
} else if (typeof value === 'object' && value.start && value.end) {
// Handle date range
whereConditions.push(`${field} BETWEEN '${value.start}' AND '${value.end}'`);
} else if (typeof value === 'boolean') {
// Handle boolean
whereConditions.push(`${field} = ${value}`);
} else {
// Handle text and other values
whereConditions.push(`${field} = '${value}'`);
}
});
if (whereConditions.length > 0) {
query += ` WHERE ${whereConditions.join(' AND ')}`;
}
// Execute query and return results
database.query(query, (err, results) => {
if (err) {
res.status(500).json({ error: err.message });
} else {
res.json({
chartLabels: results.map(row => row[xAxis]),
chartData: [{
data: results.map(row => row[yAxes]),
label: yAxes
}]
});
}
});
});
```
## Filter Presets Example
### Saving a Preset
```typescript
// Save current filter state as a preset
const presetName = 'Q4 2023 Sales';
filterService.savePreset(presetName);
// Preset is now available in the presets list
const presets = filterService.getPresets(); // ['Q4 2023 Sales', ...]
```
### Loading a Preset
```typescript
// Load a saved preset
filterService.loadPreset('Q4 2023 Sales');
// All charts will automatically refresh with the preset filters
```
## Cross-Filtering Example
```typescript
// In a chart component, handle click events
onBarClick(event: any): void {
const clickedCategory = event.active[0]._model.label;
// Update the category filter
this.filterService.updateFilterValue('category', clickedCategory);
// All other charts will automatically update
}
```
## URL Synchronization Example
```typescript
// Update URL when filters change
this.filterService.filterState$.subscribe(filters => {
const queryParams = new URLSearchParams();
Object.keys(filters).forEach(key => {
const value = filters[key];
if (value !== undefined && value !== null && value !== '') {
if (typeof value === 'object') {
if (value.hasOwnProperty('start') && value.hasOwnProperty('end')) {
// Date range
if (value.start) queryParams.append(`${key}_start`, value.start);
if (value.end) queryParams.append(`${key}_end`, value.end);
} else {
// Other objects as JSON
queryParams.append(key, JSON.stringify(value));
}
} else {
// Simple values
queryParams.append(key, value.toString());
}
}
});
// Update browser URL
const newUrl = `${window.location.pathname}?${queryParams.toString()}`;
window.history.replaceState({}, '', newUrl);
});
// Load filters from URL on page load
ngOnInit(): void {
const urlParams = new URLSearchParams(window.location.search);
const filterState: any = {};
// Parse URL parameters back into filter state
for (const [key, value] of urlParams.entries()) {
if (key.endsWith('_start')) {
const fieldName = key.replace('_start', '');
filterState[fieldName] = filterState[fieldName] || {};
filterState[fieldName].start = value;
} else if (key.endsWith('_end')) {
const fieldName = key.replace('_end', '');
filterState[fieldName] = filterState[fieldName] || {};
filterState[fieldName].end = value;
} else {
try {
filterState[key] = JSON.parse(value);
} catch (e) {
filterState[key] = value;
}
}
}
// Apply URL filters
Object.keys(filterState).forEach(key => {
this.filterService.updateFilterValue(key, filterState[key]);
});
}
```
## Data Flow Diagram
```mermaid
sequenceDiagram
participant User
participant CommonFilterComponent
participant FilterService
participant ChartComponent
participant API
User->>CommonFilterComponent: Configure filters
CommonFilterComponent->>FilterService: Update filter state
FilterService->>ChartComponent: Notify filter change
ChartComponent->>FilterService: Request current filters
FilterService-->>ChartComponent: Return filter values
ChartComponent->>API: Fetch data with filters
API-->>ChartComponent: Return filtered data
ChartComponent->>User: Display updated chart
```
This implementation provides a robust, scalable solution for managing common filters across multiple dashboard components with real-time updates and flexible configuration options.# Common Filter System - Example Usage
## Dashboard Layout
```
+-----------------------------------------------------+
| Common Filter Widget (Draggable) |
| |
| [Category ▼] [Status ▼] [Date Range] [Active -toggle]|
| [Save Preset] [Preset Selector] [Reset] |
+-----------------------------------------------------+
| +-----------+ +-----------+ +-----------+ |
| | Bar Chart | | Line Chart| | Pie Chart | |
| | | | | | | |
| | | | | | | |
| +-----------+ +-----------+ +-----------+ |
| |
| +-----------+ +-----------+ +-----------+ |
| | Table | | Map | | KPI Cards | |
| | | | | | | |
| | | | | | | |
| +-----------+ +-----------+ +-----------+ |
+-----------------------------------------------------+
```
## Filter Configuration Example
### 1. Creating Filter Definitions
```typescript
// In CommonFilterComponent or dashboard initialization
const filters: Filter[] = [
{
id: 'category',
field: 'product_category',
label: 'Category',
type: 'dropdown',
options: ['Electronics', 'Clothing', 'Home & Garden', 'Books', 'Sports']
},
{
id: 'status',
field: 'order_status',
label: 'Status',
type: 'multiselect',
options: ['Pending', 'Processing', 'Shipped', 'Delivered', 'Cancelled']
},
{
id: 'date_range',
field: 'order_date',
label: 'Order Date',
type: 'date-range'
},
{
id: 'active',
field: 'is_active',
label: 'Active Orders',
type: 'toggle'
}
];
// Set filters in the service
filterService.setFilters(filters);
```
### 2. Setting Initial Filter Values
```typescript
// Set initial values
filterService.updateFilterValue('category', 'Electronics');
filterService.updateFilterValue('status', ['Processing', 'Shipped']);
filterService.updateFilterValue('date_range', {
start: '2023-01-01',
end: '2023-12-31'
});
filterService.updateFilterValue('active', true);
```
### 3. Chart Component Integration
```typescript
// In bar-chart.component.ts
export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
private subscriptions: Subscription[] = [];
constructor(
private dashboardService: Dashboard3Service,
private filterService: FilterService
) { }
ngOnInit(): void {
// Subscribe to filter changes
this.subscriptions.push(
this.filterService.filterState$.subscribe(filters => {
this.refreshData();
})
);
// Load initial data
this.fetchChartData();
}
fetchChartData(): void {
// Get current filter values
const filterValues = this.filterService.getFilterValues();
// Build filter parameters for API
let filterParams = '';
if (Object.keys(filterValues).length > 0) {
const filterObj = {};
// Add base filters first
if (this.baseFilters && this.baseFilters.length > 0) {
this.baseFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
}
// Add common filters
Object.keys(filterValues).forEach(key => {
const value = filterValues[key];
if (value !== undefined && value !== null && value !== '') {
filterObj[key] = value;
}
});
if (Object.keys(filterObj).length > 0) {
filterParams = JSON.stringify(filterObj);
}
}
// Make API call with filters
this.dashboardService.getChartData(
this.table,
'bar',
this.xAxis,
this.yAxis,
this.connection,
'',
'',
filterParams
).subscribe(data => {
// Handle response
this.processChartData(data);
});
}
refreshData(): void {
this.fetchChartData();
}
ngOnDestroy(): void {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
}
```
### 4. API Endpoint Example
```javascript
// Backend API endpoint example
app.get('/chart/getdashjson/bar', (req, res) => {
const {
tableName,
xAxis,
yAxes,
sureId,
filters // JSON string of filters
} = req.query;
// Parse filters
let filterObj = {};
if (filters) {
try {
filterObj = JSON.parse(filters);
} catch (e) {
console.error('Failed to parse filters:', e);
}
}
// Build database query with filters
let query = `SELECT ${xAxis}, ${yAxes} FROM ${tableName}`;
const whereConditions = [];
// Add filter conditions
Object.keys(filterObj).forEach(field => {
const value = filterObj[field];
if (Array.isArray(value)) {
// Handle multiselect (IN clause)
const values = value.map(v => `'${v}'`).join(',');
whereConditions.push(`${field} IN (${values})`);
} else if (typeof value === 'object' && value.start && value.end) {
// Handle date range
whereConditions.push(`${field} BETWEEN '${value.start}' AND '${value.end}'`);
} else if (typeof value === 'boolean') {
// Handle boolean
whereConditions.push(`${field} = ${value}`);
} else {
// Handle text and other values
whereConditions.push(`${field} = '${value}'`);
}
});
if (whereConditions.length > 0) {
query += ` WHERE ${whereConditions.join(' AND ')}`;
}
// Execute query and return results
database.query(query, (err, results) => {
if (err) {
res.status(500).json({ error: err.message });
} else {
res.json({
chartLabels: results.map(row => row[xAxis]),
chartData: [{
data: results.map(row => row[yAxes]),
label: yAxes
}]
});
}
});
});
```
## Filter Presets Example
### Saving a Preset
```typescript
// Save current filter state as a preset
const presetName = 'Q4 2023 Sales';
filterService.savePreset(presetName);
// Preset is now available in the presets list
const presets = filterService.getPresets(); // ['Q4 2023 Sales', ...]
```
### Loading a Preset
```typescript
// Load a saved preset
filterService.loadPreset('Q4 2023 Sales');
// All charts will automatically refresh with the preset filters
```
## Cross-Filtering Example
```typescript
// In a chart component, handle click events
onBarClick(event: any): void {
const clickedCategory = event.active[0]._model.label;
// Update the category filter
this.filterService.updateFilterValue('category', clickedCategory);
// All other charts will automatically update
}
```
## URL Synchronization Example
```typescript
// Update URL when filters change
this.filterService.filterState$.subscribe(filters => {
const queryParams = new URLSearchParams();
Object.keys(filters).forEach(key => {
const value = filters[key];
if (value !== undefined && value !== null && value !== '') {
if (typeof value === 'object') {
if (value.hasOwnProperty('start') && value.hasOwnProperty('end')) {
// Date range
if (value.start) queryParams.append(`${key}_start`, value.start);
if (value.end) queryParams.append(`${key}_end`, value.end);
} else {
// Other objects as JSON
queryParams.append(key, JSON.stringify(value));
}
} else {
// Simple values
queryParams.append(key, value.toString());
}
}
});
// Update browser URL
const newUrl = `${window.location.pathname}?${queryParams.toString()}`;
window.history.replaceState({}, '', newUrl);
});
// Load filters from URL on page load
ngOnInit(): void {
const urlParams = new URLSearchParams(window.location.search);
const filterState: any = {};
// Parse URL parameters back into filter state
for (const [key, value] of urlParams.entries()) {
if (key.endsWith('_start')) {
const fieldName = key.replace('_start', '');
filterState[fieldName] = filterState[fieldName] || {};
filterState[fieldName].start = value;
} else if (key.endsWith('_end')) {
const fieldName = key.replace('_end', '');
filterState[fieldName] = filterState[fieldName] || {};
filterState[fieldName].end = value;
} else {
try {
filterState[key] = JSON.parse(value);
} catch (e) {
filterState[key] = value;
}
}
}
// Apply URL filters
Object.keys(filterState).forEach(key => {
this.filterService.updateFilterValue(key, filterState[key]);
});
}
```
## Data Flow Diagram
```mermaid
sequenceDiagram
participant User
participant CommonFilterComponent
participant FilterService
participant ChartComponent
participant API
User->>CommonFilterComponent: Configure filters
CommonFilterComponent->>FilterService: Update filter state
FilterService->>ChartComponent: Notify filter change
ChartComponent->>FilterService: Request current filters
FilterService-->>ChartComponent: Return filter values
ChartComponent->>API: Fetch data with filters
API-->>ChartComponent: Return filtered data
ChartComponent->>User: Display updated chart
```
This implementation provides a robust, scalable solution for managing common filters across multiple dashboard components with real-time updates and flexible configuration options.

View File

@@ -1,219 +0,0 @@
# Common Filter Implementation Guide
## Overview
This implementation provides a centralized filter system that allows users to control multiple charts simultaneously through a draggable filter widget. The system consists of:
1. **FilterService** - Central service managing filter definitions and state
2. **CommonFilterComponent** - Draggable widget for configuring filters
3. **Chart Components** - Updated chart components that subscribe to filter changes
## Architecture
```mermaid
graph TB
A[FilterService] --> B[CommonFilterComponent]
A --> C[BarChartComponent]
A --> D[LineChartComponent]
A --> E[OtherChartComponents]
F[User Interaction] --> B
```
## Key Components
### 1. FilterService
The central service that manages:
- Filter definitions (type, options, labels)
- Current filter values
- Filter presets
- Query parameter generation for API calls
#### Methods:
- `addFilter()` - Add a new filter
- `removeFilter()` - Remove a filter
- `updateFilterValue()` - Update a filter's value
- `getFilterValues()` - Get current filter values
- `resetFilters()` - Reset all filters to default values
- `savePreset()` - Save current filter state as a preset
- `loadPreset()` - Load a saved preset
- `buildQueryParams()` - Generate query parameters for API calls
### 2. CommonFilterComponent
A draggable widget that provides the UI for:
- Adding/removing filters
- Configuring filter properties
- Setting filter values
- Managing presets
### 3. Chart Components
Updated chart components that:
- Subscribe to filter changes
- Automatically refresh when filters change
- Include filter values in API calls
## Implementation Details
### Filter Types Supported
1. **Text** - Simple text input
2. **Dropdown** - Single selection from options
3. **Multiselect** - Multiple selection from options
4. **Date Range** - Start and end date selection
5. **Toggle** - Boolean on/off switch
### Data Flow
1. User adds/configures filters in CommonFilterComponent
2. FilterService stores filter definitions and values
3. Chart components subscribe to FilterService.filterState$
4. When filters change, charts automatically refresh
5. Filter values are included in API calls as query parameters
### API Integration
Filters are passed to the backend as query parameters:
- Text filter: `name=John`
- Dropdown filter: `category=A`
- Multiselect filter: `tags=["tag1","tag2"]`
- Date range filter: `date_start=2023-01-01&date_end=2023-12-31`
- Toggle filter: `isActive=true`
## Usage Examples
### Adding the Common Filter Widget
1. Drag the "Common Filter" widget from the component palette
2. Place it at the top of the dashboard
3. Configure filters as needed
### Creating Filters
```typescript
// Add a text filter
const textFilter: Filter = {
id: 'name',
field: 'name',
label: 'Name',
type: 'text'
};
// Add a dropdown filter
const dropdownFilter: Filter = {
id: 'category',
field: 'category',
label: 'Category',
type: 'dropdown',
options: ['A', 'B', 'C']
};
```
### Consuming Filters in Charts
```typescript
// In chart component
constructor(private filterService: FilterService) {}
ngOnInit(): void {
// Subscribe to filter changes
this.subscriptions.push(
this.filterService.filterState$.subscribe(filters => {
this.fetchChartData();
})
);
}
fetchChartData(): void {
// Get current filter values
const filterValues = this.filterService.getFilterValues();
// Include in API call
const queryParams = this.filterService.buildQueryParams();
// Use queryParams in API call
}
```
## JSON Structures
### Filter Definition
```json
{
"id": "filter_123456",
"field": "category",
"label": "Category",
"type": "dropdown",
"options": ["Electronics", "Clothing", "Books"],
"value": "Electronics"
}
```
### Filter State
```json
{
"category": "Electronics",
"price_range": {
"start": 100,
"end": 500
},
"in_stock": true,
"tags": ["sale", "featured"]
}
```
### API Response Format
```json
{
"chartLabels": ["Jan", "Feb", "Mar"],
"chartData": [
{
"data": [10, 20, 30],
"label": "Sales"
}
]
}
```
## Cross-Filtering Support
Charts can also support cross-filtering where clicking on one chart updates the common filters:
1. Implement click handlers in chart components
2. Update filter values through FilterService
3. All other charts automatically refresh
Example:
```typescript
onChartClick(dataPoint: any): void {
// Update a filter based on chart click
this.filterService.updateFilterValue('category', dataPoint.category);
}
```
## Preset Management
Users can save and load filter presets:
1. Configure desired filters
2. Enter a preset name and click "Save Preset"
3. Select preset from dropdown to load
4. Delete presets as needed
## URL Synchronization
Filter states can be synchronized with URL query parameters for shareable dashboards:
1. On filter change, update URL query parameters
2. On page load, read filters from URL
3. Apply filters to charts
## Optional Enhancements
1. **Real-time Updates** - WebSocket integration for live data
2. **Advanced Filtering** - Custom filter expressions
3. **Filter Dependencies** - Conditional filters based on other filter values
4. **Analytics** - Track most used filters and combinations

View File

@@ -1,68 +0,0 @@
.chart-wrapper {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
.chart-header {
padding: 10px 15px;
background: #f8f8f8;
border-bottom: 1px solid #eee;
h5 {
margin: 0;
color: #333;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.chart-container {
flex: 1;
padding: 15px;
overflow: auto;
position: relative;
// Ensure chart containers fill available space
::ng-deep canvas {
max-width: 100%;
max-height: 100%;
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.chart-wrapper {
.chart-header {
padding: 8px 12px;
h5 {
font-size: 14px;
}
}
.chart-container {
padding: 10px;
}
}
}
@media (max-width: 480px) {
.chart-wrapper {
.chart-header {
padding: 6px 10px;
h5 {
font-size: 13px;
}
}
.chart-container {
padding: 8px;
}
}
}

View File

@@ -1,39 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChartWrapperComponent } from './chart-wrapper.component';
import { FilterService } from './filter.service';
describe('ChartWrapperComponent', () => {
let component: ChartWrapperComponent;
let fixture: ComponentFixture<ChartWrapperComponent>;
let filterService: FilterService;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ChartWrapperComponent],
providers: [FilterService]
})
.compileComponents();
fixture = TestBed.createComponent(ChartWrapperComponent);
component = fixture.componentInstance;
filterService = TestBed.inject(FilterService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should subscribe to filter changes on init', () => {
spyOn(filterService.filterState$, 'subscribe');
component.ngOnInit();
expect(filterService.filterState$.subscribe).toHaveBeenCalled();
});
it('should unsubscribe from filter changes on destroy', () => {
component.ngOnInit();
spyOn(component['filterSubscription']!, 'unsubscribe');
component.ngOnDestroy();
expect(component['filterSubscription']!.unsubscribe).toHaveBeenCalled();
});
});

View File

@@ -1,111 +0,0 @@
import { Component, Input, OnInit, OnDestroy, ComponentRef, ViewChild, ViewContainerRef, HostListener } from '@angular/core';
import { Subscription } from 'rxjs';
import { FilterService } from './filter.service';
@Component({
selector: 'app-chart-wrapper',
template: `
<div class="chart-wrapper">
<div class="chart-header" *ngIf="chartTitle">
<h5>{{ chartTitle }}</h5>
</div>
<div class="chart-container">
<ng-container #chartContainer></ng-container>
</div>
</div>
`,
styleUrls: ['./chart-wrapper.component.scss']
})
export class ChartWrapperComponent implements OnInit, OnDestroy {
@Input() chartComponent: any;
@Input() chartInputs: any = {};
@Input() chartTitle: string = '';
@ViewChild('chartContainer', { read: ViewContainerRef }) chartContainer!: ViewContainerRef;
private componentRef: ComponentRef<any> | null = null;
private filterSubscription: Subscription | null = null;
constructor(private filterService: FilterService) { }
ngOnInit(): void {
this.loadChartComponent();
this.subscribeToFilters();
}
ngOnDestroy(): void {
if (this.filterSubscription) {
this.filterSubscription.unsubscribe();
}
if (this.componentRef) {
this.componentRef.destroy();
}
}
// Handle window resize events
@HostListener('window:resize', ['$event'])
onResize(event: any) {
// Notify the chart component to resize if it has a resize method
if (this.componentRef && this.componentRef.instance) {
const chartInstance = this.componentRef.instance;
// If it's a chart component with an onResize method, call it
if (chartInstance.onResize && typeof chartInstance.onResize === 'function') {
chartInstance.onResize();
}
// If it's a chart component with a chart property (from BaseChartDirective), resize it
if (chartInstance.chart && typeof chartInstance.chart.resize === 'function') {
setTimeout(() => {
chartInstance.chart.resize();
}, 100);
}
}
}
private loadChartComponent(): void {
if (this.chartContainer && this.chartComponent) {
this.chartContainer.clear();
const factory = this.chartContainer.createComponent(this.chartComponent);
this.componentRef = factory;
// Set initial inputs
Object.keys(this.chartInputs).forEach(key => {
factory.instance[key] = this.chartInputs[key];
});
}
}
private subscribeToFilters(): void {
this.filterSubscription = this.filterService.filterState$.subscribe(filterValues => {
this.updateChartWithFilters(filterValues);
});
}
private updateChartWithFilters(filterValues: any): void {
if (this.componentRef) {
// Add filter values to chart inputs
const updatedInputs = {
...this.chartInputs,
filterValues: filterValues,
// Pass the query params string for easy API integration
filterQueryParams: this.filterService.buildQueryParams()
};
// Update chart component inputs
Object.keys(updatedInputs).forEach(key => {
this.componentRef!.instance[key] = updatedInputs[key];
});
// Trigger change detection if the component has a method for it
if (this.componentRef!.instance.ngOnChanges) {
// We can't easily trigger ngOnChanges manually, but the input update should trigger it
}
// If the chart component has a method to refresh data, call it
if (this.componentRef!.instance.refreshData) {
this.componentRef!.instance.refreshData();
}
}
}
}

View File

@@ -1,128 +0,0 @@
<div class="common-filter-container">
<!-- Filter Header -->
<div class="filter-header">
<h4>Common Filters</h4>
<button class="btn btn-sm btn-primary" (click)="addFilter()">
<clr-icon shape="plus"></clr-icon> Add Filter
</button>
</div>
<!-- Presets Section -->
<div class="presets-section" *ngIf="presets.length > 0">
<div class="preset-controls">
<select [(ngModel)]="activePreset" (change)="loadPreset(activePreset || '')" class="clr-select">
<option value="">Select Preset</option>
<option *ngFor="let preset of presets" [value]="preset">{{ preset }}</option>
</select>
<button class="btn btn-sm btn-danger" (click)="resetFilters()">
<clr-icon shape="undo"></clr-icon> Reset
</button>
</div>
</div>
<!-- Save Preset Section -->
<div class="save-preset-section">
<div class="clr-input-group">
<input type="text" [(ngModel)]="newPresetName" placeholder="Preset name" class="clr-input">
<div class="clr-input-group-btn">
<button class="btn btn-sm btn-success" (click)="savePreset()" [disabled]="!newPresetName.trim()">
<clr-icon shape="floppy"></clr-icon> Save Preset
</button>
</div>
</div>
</div>
<!-- Filters Form -->
<form [formGroup]="filterForm" class="filters-form">
<div class="filters-grid">
<div *ngFor="let filter of filters" class="filter-item">
<div class="filter-header">
<span class="filter-label">{{ filter.label }}</span>
<button class="btn btn-icon btn-danger btn-sm" (click)="removeFilter(filter.id)">
<clr-icon shape="trash"></clr-icon>
</button>
</div>
<!-- Filter Type Selector -->
<div class="clr-form-control">
<select [(ngModel)]="filter.type" (ngModelChange)="updateFilter(filter.id, 'type', $event)"
[ngModelOptions]="{standalone: true}" class="clr-select filter-type-select">
<option value="text">Text</option>
<option value="dropdown">Dropdown</option>
<option value="multiselect">Multi-Select</option>
<option value="date-range">Date Range</option>
<option value="toggle">Toggle</option>
</select>
</div>
<!-- Filter Label Input -->
<div class="clr-form-control">
<input type="text" [(ngModel)]="filter.label" (ngModelChange)="updateFilter(filter.id, 'label', $event)"
[ngModelOptions]="{standalone: true}" placeholder="Filter Label" class="clr-input">
</div>
<!-- Filter Field Input -->
<div class="clr-form-control">
<input type="text" [(ngModel)]="filter.field" (ngModelChange)="updateFilter(filter.id, 'field', $event)"
[ngModelOptions]="{standalone: true}" placeholder="Field Name" class="clr-input">
</div>
<!-- Filter Options (for dropdown and multiselect) -->
<div class="clr-form-control" *ngIf="filter.type === 'dropdown' || filter.type === 'multiselect'">
<textarea [(ngModel)]="filter.options" (ngModelChange)="updateFilter(filter.id, 'options', $event ? $event.split(',') : [])"
[ngModelOptions]="{standalone: true}" placeholder="Options (comma separated)" class="clr-textarea"></textarea>
</div>
<!-- Filter Controls Based on Type -->
<div class="filter-control" [ngSwitch]="filter.type">
<!-- Text Filter -->
<div *ngSwitchCase="'text'">
<input type="text" [formControlName]="filter.id" placeholder="Enter text" class="clr-input">
</div>
<!-- Dropdown Filter -->
<div *ngSwitchCase="'dropdown'">
<select [formControlName]="filter.id" class="clr-select">
<option value="">Select an option</option>
<option *ngFor="let option of filter.options" [value]="option">{{ option }}</option>
</select>
</div>
<!-- Multi-Select Filter -->
<div *ngSwitchCase="'multiselect'">
<select [formControlName]="filter.id" multiple class="clr-select multiselect">
<option *ngFor="let option of filter.options" [value]="option">{{ option }}</option>
</select>
</div>
<!-- Date Range Filter -->
<div *ngSwitchCase="'date-range'" class="date-range-controls">
<div class="clr-form-control">
<label>Start Date</label>
<input type="date" [ngModel]="filterForm.get(filter.id + '.start')?.value"
(ngModelChange)="onDateRangeChange(filter.id, { start: $event, end: filterForm.get(filter.id + '.end')?.value })"
[ngModelOptions]="{standalone: true}" class="clr-input">
</div>
<div class="clr-form-control">
<label>End Date</label>
<input type="date" [ngModel]="filterForm.get(filter.id + '.end')?.value"
(ngModelChange)="onDateRangeChange(filter.id, { start: filterForm.get(filter.id + '.start')?.value, end: $event })"
[ngModelOptions]="{standalone: true}" class="clr-input">
</div>
</div>
<!-- Toggle Filter -->
<div *ngSwitchCase="'toggle'" class="toggle-control">
<input type="checkbox" [formControlName]="filter.id" clrToggle class="clr-toggle">
<label>{{ filter.label }}</label>
</div>
</div>
</div>
</div>
</form>
<!-- No Filters Message -->
<div class="no-filters" *ngIf="filters.length === 0">
<p>No filters added yet. Click "Add Filter" to create your first filter.</p>
</div>
</div>

View File

@@ -1,192 +0,0 @@
.common-filter-container {
padding: 15px;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 20px;
height: 100%;
width: 100%;
box-sizing: border-box;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 10px;
h4 {
margin: 0;
color: #333;
flex: 1;
}
.btn {
white-space: nowrap;
}
}
.presets-section {
margin-bottom: 15px;
.preset-controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
select {
flex: 1;
min-width: 150px;
}
.btn {
white-space: nowrap;
}
}
}
.save-preset-section {
margin-bottom: 15px;
.clr-input-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
.clr-input {
flex: 1;
min-width: 150px;
}
.clr-input-group-btn {
.btn {
white-space: nowrap;
}
}
}
}
.filters-form {
.filters-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
.filter-item {
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 15px;
display: flex;
flex-direction: column;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.filter-label {
font-weight: bold;
color: #333;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.filter-type-select {
width: 100%;
margin-bottom: 10px;
}
.clr-form-control {
margin-bottom: 10px;
input, select, textarea {
width: 100%;
min-width: 0; // Allow flexbox to shrink items
}
}
.date-range-controls {
display: flex;
gap: 10px;
flex-direction: column;
.clr-form-control {
flex: 1;
}
@media (min-width: 480px) {
flex-direction: row;
}
}
.toggle-control {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.multiselect {
height: 100px;
}
}
}
}
.no-filters {
text-align: center;
padding: 20px;
color: #666;
font-style: italic;
}
}
// Responsive design for smaller screens
@media (max-width: 768px) {
.common-filter-container {
padding: 10px;
.filters-form {
.filters-grid {
grid-template-columns: 1fr;
gap: 10px;
}
}
.filter-header {
flex-direction: column;
align-items: stretch;
}
.presets-section {
.preset-controls {
flex-direction: column;
}
}
.save-preset-section {
.clr-input-group {
flex-direction: column;
}
}
}
}
@media (max-width: 480px) {
.common-filter-container {
.date-range-controls {
flex-direction: column;
}
.filter-item {
padding: 10px;
}
}
}

Some files were not shown because too many files have changed in this diff Show More