builder
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
export class ReportBuilder {
|
export class ReportBuilder {
|
||||||
public report_id: number;
|
public report_id: number;
|
||||||
public report_name: string;
|
public report_name:string;
|
||||||
public description: string;
|
public description: string;
|
||||||
public report_tags: string;
|
public report_tags: string;
|
||||||
public servicename: string;
|
public servicename:string;
|
||||||
// Add SureConnect reference
|
|
||||||
public sureConnectId: number | null;
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,39 +23,6 @@ export interface DashboardContentModel {
|
|||||||
component?: any;
|
component?: any;
|
||||||
name: string;
|
name: string;
|
||||||
type?: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 {
|
export interface DashboardModel {
|
||||||
@@ -100,10 +67,6 @@ export class value1{
|
|||||||
|
|
||||||
|
|
||||||
export const WidgetsMock: WidgetModel[] = [
|
export const WidgetsMock: WidgetModel[] = [
|
||||||
{
|
|
||||||
name: 'Common Filter',
|
|
||||||
identifier: 'common_filter'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Radar Chart',
|
name: 'Radar Chart',
|
||||||
identifier: 'radar_chart'
|
identifier: 'radar_chart'
|
||||||
@@ -148,4 +111,4 @@ export const WidgetsMock: WidgetModel[] = [
|
|||||||
name: 'To Do',
|
name: 'To Do',
|
||||||
identifier: 'to_do_chart'
|
identifier: 'to_do_chart'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,155 +1,39 @@
|
|||||||
<!-- Modern AI-Inspired Login Page -->
|
<!-- <router-outlet></router-outlet> -->
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Main Login Content -->
|
<!-- <div class="login-wrapper" *ngIf="showLogin2 && loginEnvironment.templateNo == 'Template 1'" [style.backgroundImage]="'url(' + loginEnvironment.imagePath + ')'">
|
||||||
<div class="login-content">
|
<form class="login">
|
||||||
<!-- Left Side - Branding -->
|
<section class="title">
|
||||||
<div class="login-branding">
|
<h3 class="welcome">Welcome to</h3>
|
||||||
<div class="brand-content">
|
{{ loginEnvironment.companyName }}
|
||||||
<div class="logo-section">
|
<h5 class="hint">Use your ID to sign in OR <a [routerLink]="['/email-verification']" class="signup">create one now </a> </h5>
|
||||||
<div class="logo-container">
|
</section>
|
||||||
<img class="logo-img" src="assets/images/icon/micrologo.png" alt="CloudnSure Logo">
|
<div class="login-group">
|
||||||
<div class="logo-glow"></div>
|
<clr-input-container>
|
||||||
</div>
|
<label class="clr-sr-only">Username</label>
|
||||||
<h1 class="brand-title">
|
<input type="text" name="username" clrInput autocomplete="off" [(ngModel)]="model.email"
|
||||||
<ng-container *ngIf="loginEnvironment?.companyName; else defaultTitle">
|
placeholder="Username (sysadmin)"/>
|
||||||
{{ loginEnvironment.companyName }}
|
</clr-input-container>
|
||||||
</ng-container>
|
<clr-password-container>
|
||||||
<ng-template #defaultTitle>
|
<label class="clr-sr-only">Password</label>
|
||||||
CloudnSure
|
<input type="password" name="password" clrPassword id="login_password" autocomplete="off" [(ngModel)]="model.password"
|
||||||
</ng-template>
|
placeholder="Password (test3)"/>
|
||||||
</h1>
|
</clr-password-container>
|
||||||
<p class="brand-subtitle">Enterprise Platform</p>
|
<clr-checkbox-wrapper>
|
||||||
</div>
|
<input clrCheckbox type="checkbox" id="rememberme">
|
||||||
|
<label for="rememberme"> Remember me </label>
|
||||||
<div class="brand-features">
|
</clr-checkbox-wrapper>
|
||||||
<div class="feature-item">
|
|
||||||
<clr-icon shape="shield" class="feature-icon"></clr-icon>
|
<div class="error active" *ngIf="isError">
|
||||||
<span>Secure & Reliable</span>
|
Invalid user name or password
|
||||||
</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>
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
</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>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- 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') + ')'">
|
<div [class]="getWrapperClass()" *ngIf="showLogin2 && loginEnvironment.templateNo == '<templateNo>'" [style.backgroundImage]="'url(' + ('../../../../assets/images/new.png') + ')'">
|
||||||
<form class="login">
|
<form class="login">
|
||||||
<section class="title">
|
<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') + ')'">
|
<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">
|
<form class="login">
|
||||||
<section class="title">
|
<section class="title">
|
||||||
@@ -259,4 +143,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
@@ -1,653 +1,23 @@
|
|||||||
// ========================================
|
.tamplate1 {
|
||||||
// MODERN AI-INSPIRED LOGIN PAGE STYLES
|
background-image: url(../../../../assets/images/new.png);
|
||||||
// ========================================
|
background-repeat: no-repeat;
|
||||||
|
background-size: 60%;
|
||||||
@import '../../../../styles/design-tokens';
|
background-position: right center;
|
||||||
|
border: 1px solid #000;
|
||||||
// Modern Login Container
|
}
|
||||||
.modern-login-container {
|
|
||||||
min-height: 100vh;
|
.tamplate2 {
|
||||||
display: flex;
|
background-image: url(../../../../assets/images/new.png);
|
||||||
align-items: center;
|
background-repeat: no-repeat;
|
||||||
justify-content: center;
|
background-size: 60%;
|
||||||
position: relative;
|
background-position: left center;
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
background: linear-gradient(135deg, $primary-50 0%, $primary-100 50%, $secondary-50 100%);
|
justify-content: flex-end;
|
||||||
}
|
align-items: center;
|
||||||
|
border: 1px solid #000;
|
||||||
// Background Elements
|
}
|
||||||
.login-background {
|
.signup {
|
||||||
position: absolute;
|
cursor: pointer;
|
||||||
top: 0;
|
// color:#80b0ff;
|
||||||
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 {
|
|
||||||
background-image: url(../../../../assets/images/new.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 60%;
|
|
||||||
background-position: right center;
|
|
||||||
border: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tamplate2 {
|
|
||||||
background-image: url(../../../../assets/images/new.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 60%;
|
|
||||||
background-position: left center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
border: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ export class LoginPageComponent implements OnInit {
|
|||||||
email = '';
|
email = '';
|
||||||
password = '';
|
password = '';
|
||||||
isError = false;
|
isError = false;
|
||||||
isLoading = false;
|
|
||||||
showPassword = false;
|
|
||||||
|
|
||||||
model: any = {};
|
model: any = {};
|
||||||
errMsg: string = '';
|
errMsg: string = '';
|
||||||
@@ -73,80 +71,32 @@ export class LoginPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLogin() {
|
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
|
// tslint:disable-next-line:max-line-length
|
||||||
this.loginService.getToken(this.model.email, this.model.password)
|
this.loginService.getToken(this.model.email, this.model.password)
|
||||||
.subscribe(
|
.subscribe(resp => {
|
||||||
resp => {
|
if (resp.user === undefined || resp.user.token === undefined || resp.user.token === "INVALID") {
|
||||||
console.log('API Response received:', resp);
|
this.errMsg = 'Checking Email or password';
|
||||||
this.isLoading = false;
|
return;
|
||||||
|
}
|
||||||
// Handle different response formats
|
this.router.navigate([resp.landingPage]);// add , {skipLocationChange: true}
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (resp.user === undefined || resp.user.token === undefined || resp.user.token === "INVALID") {
|
|
||||||
this.isError = true;
|
|
||||||
this.errMsg = 'Invalid email or password';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success - navigate to landing page
|
|
||||||
console.log('Login successful, navigating to:', resp.landingPage);
|
|
||||||
this.router.navigate([resp.landingPage]);
|
|
||||||
},
|
|
||||||
(errResponse: HttpErrorResponse) => {
|
(errResponse: HttpErrorResponse) => {
|
||||||
console.log('API Error received:', errResponse);
|
|
||||||
this.isLoading = false;
|
|
||||||
this.isError = true;
|
|
||||||
|
|
||||||
switch (errResponse.status) {
|
switch (errResponse.status) {
|
||||||
case 401:
|
case 401:
|
||||||
this.errMsg = 'Email or password is incorrect';
|
this.errMsg = 'Email or password is incorrect';
|
||||||
break;
|
break;
|
||||||
case 404:
|
case 404:
|
||||||
this.errMsg = 'Service not found';
|
this.errMsg = 'Service not found';
|
||||||
break;
|
|
||||||
case 408:
|
case 408:
|
||||||
this.errMsg = 'Request timeout';
|
this.errMsg = 'Request Timedout';
|
||||||
break;
|
|
||||||
case 500:
|
case 500:
|
||||||
this.errMsg = 'Internal server error';
|
this.errMsg = 'Internal Server Error';
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
this.errMsg = 'Network error - please check your connection';
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
this.errMsg = 'An error occurred. Please try again.';
|
this.errMsg = 'Server Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
goaccount(){
|
goaccount(){
|
||||||
}
|
}
|
||||||
@@ -154,17 +104,5 @@ export class LoginPageComponent implements OnInit {
|
|||||||
this.router.navigate(["../forgotpass"], { relativeTo: this.route });
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -234,8 +240,7 @@ export class Stepper_workflowComponent implements OnInit {
|
|||||||
|
|
||||||
rsModaldescription = false;
|
rsModaldescription = false;
|
||||||
goToReplaceStringdescription(row) {
|
goToReplaceStringdescription(row) {
|
||||||
this.rowSelected = row;
|
this.rowSelected = row; this.rsModaldescription = true;
|
||||||
this.rsModaldescription = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -248,39 +253,6 @@ export class Stepper_workflowComponent implements OnInit {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.router.navigate(["../demostepper/", id], { relativeTo: this.route });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateaction
|
// updateaction
|
||||||
|
|||||||
@@ -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://" [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://"><clr-icon shape="lock"></clr-icon>{{'SECURITY' | translate}}</a></li>
|
||||||
<li><a href="javascript://">{{'MENU_ACCESS_CONTROL' | translate}}</a></li>
|
<li><a href="javascript://">{{ 'MENU_ACCESS_CONTROL' | translate }}</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<section class="mac-hero">
|
<div class="clr-row">
|
||||||
<div class="mac-hero__content">
|
<div class="clr-col-6" style="display:flex ;">
|
||||||
<div class="mac-hero__icon">
|
<div style="margin-right: 25px;">
|
||||||
<clr-icon shape="shield-check"></clr-icon>
|
<h4 style="font-weight:300;">{{ 'MENU_ACCESS_CONTROL' | translate }} </h4>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div style="margin-top:27px ; position: relative;">
|
||||||
<h2 class="mac-hero__title">{{'MENU_ACCESS_CONTROL' | translate}}</h2>
|
<span class="label label-light-blue" style="display: inline;">{{ 'EDIT_MODE' | translate }}</span>
|
||||||
<p class="mac-hero__subtitle">{{'SECURITY' | translate}}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mac-hero__actions">
|
<div class="clr-col-6" style="text-align: right;">
|
||||||
<span class="mac-chip">{{'EDIT_MODE' | translate}}</span>
|
{{ 'FOR' | translate }} <select id="" style="height: 30px;" (change)="idofselected($event.target.value)">
|
||||||
<div class="mac-sep"></div>
|
|
||||||
<label class="mac-label">{{'FOR' | translate}}</label>
|
|
||||||
<select class="mac-select" (change)="idofselected($event.target.value)">
|
|
||||||
<option *ngFor="let sub of givendata" [value]="sub.usrGrp" [selected]="sub.usrGrp== 40">{{sub.groupName}}</option>
|
<option *ngFor="let sub of givendata" [value]="sub.usrGrp" [selected]="sub.usrGrp== 40">{{sub.groupName}}</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="add" class="mac-btn mac-btn-primary" (click)="getbyuseriddata()">
|
<button id="add" class="btn btn-primary" style="margin-left: 20px;" (click)="getbyuseriddata()"> {{ 'RELOAD' |
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
translate }} </button>
|
||||||
<span class="mac-btn-text">{{'RELOAD' | translate}}</span>
|
<span *ngIf="!toggle">{{ 'SHOW_ALL' | translate }} </span><span *ngIf="toggle">{{ 'ONLY_MAIN_MENU' | translate
|
||||||
</button>
|
}}</span>
|
||||||
<div class="mac-toggle">
|
<label class="switch">
|
||||||
<span class="mac-toggle__label" *ngIf="!toggle">{{'SHOW_ALL' | translate}}</span>
|
<input type="checkbox" ([ngModel])="toggle" checked (click)="toggleCheckbox()">
|
||||||
<span class="mac-toggle__label" *ngIf="toggle">{{'ONLY_MAIN_MENU' | translate}}</span>
|
<span class="slider round"></span>
|
||||||
<label class="switch">
|
</label>
|
||||||
<input type="checkbox" ([ngModel])="toggle" checked (click)="toggleCheckbox()">
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
<div class="mac-container">
|
<div class="dg-wrapper">
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<div class="clr-col-12 mac-actions">
|
|
||||||
<button id="add" class="mac-btn mac-btn-primary" (click)="modaladd()" [disabled]="!toggle">
|
<div class="clr-col-12" style="text-align: right;">
|
||||||
<clr-icon shape="plus"></clr-icon>
|
|
||||||
<span>{{'ADD' | translate}}</span>
|
<button id="add" class="btn btn-primary" style="margin-left: 20px;" (click)="modaladd()" [disabled]="!toggle">
|
||||||
</button>
|
<clr-icon shape="plus"></clr-icon>{{ 'ADD' | translate }} </button>
|
||||||
<div *ngIf="!toggle" class="error-message mac-hint">
|
<div *ngIf="!toggle" class="error-message" style="color: red; font-size: 14px; margin-top: 5px;">
|
||||||
{{'CLOSE_TOGGLE_TO_ACTIVATE_ADD_BUTTON' | translate}}
|
{{ ' Close toggle to activate Add button. ' | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
<clr-dg-placeholder>
|
||||||
<ng-template #loadingSpinner>
|
<ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
|
||||||
<clr-spinner>{{'LOADING' | translate}} ... </clr-spinner>
|
<div *ngIf="alldata?.length === 0;else loadingSpinner">{{ 'NO_DATA_AVAILABLE' | translate }}</div>
|
||||||
</ng-template>
|
|
||||||
<div *ngIf="alldata?.length === 0;else loadingSpinner">{{'NO_DATA_AVAILABLE' | translate}}</div>
|
|
||||||
</clr-dg-placeholder>
|
</clr-dg-placeholder>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'no'" class="mac-col mac-col--no">
|
<clr-dg-column [clrDgField]="'no'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
{{ 'NO' | translate }}
|
||||||
<span class="mac-col-title">
|
</ng-container></clr-dg-column>
|
||||||
<clr-icon shape="hashtag"></clr-icon> {{'NO' | translate}}
|
<clr-dg-column [clrDgField]="'menuname'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</span>
|
{{ 'MENU_ITEM_NAME' | translate }}
|
||||||
</ng-container>
|
</ng-container></clr-dg-column>
|
||||||
</clr-dg-column>
|
<clr-dg-column [clrDgField]="'mvisible'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-dg-column [clrDgField]="'menuname'" class="mac-col mac-col--name">
|
{{ 'VIEW' | translate }}
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
</ng-container></clr-dg-column>
|
||||||
<span class="mac-col-title">
|
<clr-dg-column [clrDgField]="'mcreate'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-icon shape="tag"></clr-icon> {{'MENU_ITEM_NAME' | translate}}
|
{{ 'CREATE' | translate }}
|
||||||
</span>
|
</ng-container></clr-dg-column>
|
||||||
</ng-container>
|
<clr-dg-column [clrDgField]="'medit'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-column>
|
{{ 'EDIT' | translate }}
|
||||||
<clr-dg-column [clrDgField]="'mvisible'" class="mac-col mac-col--perm">
|
</ng-container></clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'mdelete'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mac-col-title">
|
{{ 'DELETE' | translate }}
|
||||||
<clr-icon shape="eye"></clr-icon> {{'VIEW' | translate}}
|
<!-- <input type="checkbox" clrCheckbox name="mdelete" [checked]="colvalue" (change)="changedelete($event.target.checked)"> -->
|
||||||
</span>
|
</ng-container></clr-dg-column>
|
||||||
</ng-container>
|
<clr-dg-column [clrDgField]="'mquery'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-column>
|
{{ 'QUERY' | translate }}
|
||||||
<clr-dg-column [clrDgField]="'mcreate'" class="mac-col mac-col--perm">
|
</ng-container></clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'mexport'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mac-col-title">
|
{{ 'EXPORT' | translate }}
|
||||||
<clr-icon shape="plus"></clr-icon> {{'CREATE' | translate}}
|
</ng-container></clr-dg-column>
|
||||||
</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-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>{{i+1}}</clr-dg-cell>
|
||||||
<clr-dg-cell *ngIf="all.menuId==0">
|
<clr-dg-cell *ngIf="all.menuId==0"><b>{{all.menuItemDesc}}</b></clr-dg-cell>
|
||||||
<b>{{all.menuItemDesc}}</b>
|
|
||||||
</clr-dg-cell>
|
|
||||||
<clr-dg-cell *ngIf="all.menuId!==0"> {{all.menuItemDesc}}</clr-dg-cell>
|
<clr-dg-cell *ngIf="all.menuId!==0"> {{all.menuItemDesc}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mvisible"></clr-dg-cell>
|
||||||
<input type="checkbox" [checked]="selected ===all.mvisible">
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mcreate"></clr-dg-cell>
|
||||||
</clr-dg-cell>
|
<clr-dg-cell> <input type="checkbox" [checked]="selected ===all.medit"></clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mdelete"></clr-dg-cell>
|
||||||
<input type="checkbox" [checked]="selected ===all.mcreate">
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mquery"></clr-dg-cell>
|
||||||
</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.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>
|
<clr-dg-action-overflow>
|
||||||
<button class="mac-action-item" (click)="modalEdit(all)">
|
<button class="action-item" (click)="modalEdit(all)">{{ 'EDIT' | translate }} <clr-icon shape="edit"
|
||||||
{{'EDIT' | translate}}
|
class="is-error"></clr-icon></button>
|
||||||
<clr-icon shape="edit" class="is-error"></clr-icon>
|
<button class="action-item" *ngIf="all.menuId!==0" (click)="modalDelete(all)">{{ 'DELETE' | translate }}
|
||||||
</button>
|
<clr-icon shape="trash" class="is-error"></clr-icon></button>
|
||||||
<button class="mac-action-item" *ngIf="all.menuId!==0" (click)="modalDelete(all)">
|
<button class="action-item" *ngIf="all.menuId==0" (click)="modaldeletemainmenu(all)">{{ 'DELETE_MENU_SUBMENU' |
|
||||||
{{'DELETE' | translate}}
|
translate }}
|
||||||
<clr-icon shape="trash" class="is-error"></clr-icon>
|
<clr-icon shape="edit" class="is-error"></clr-icon></button>
|
||||||
</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>
|
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-title">{{'MENUNAME' | translate}}</td>
|
<td class="td-title">Menuname</td>
|
||||||
<td class="td-content"></td>
|
<td class="td-content"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</clr-dg-row-detail>
|
</clr-dg-row-detail>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
|
|
||||||
<clr-dg-footer class="mac-grid-footer">
|
<clr-dg-footer>
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{'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}}
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
{{'OF' | translate}} {{pagination.totalItems}} {{'RECORDS' | translate}}
|
of {{pagination.totalItems}} Records
|
||||||
</clr-dg-pagination>
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
|
|
||||||
<clr-datagrid class="mac-grid" [clrDgLoading]="loading" *ngIf="toggle">
|
<clr-datagrid [clrDgLoading]="loading" *ngIf="toggle">
|
||||||
<clr-dg-placeholder>
|
<clr-dg-placeholder>
|
||||||
<ng-template #loadingSpinner>
|
<ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
|
||||||
<clr-spinner>{{'LOADING' | translate}} ... </clr-spinner>
|
<div *ngIf="alldata?.length === 0;else loadingSpinner">No data available</div>
|
||||||
</ng-template>
|
|
||||||
<div *ngIf="alldata?.length === 0;else loadingSpinner">{{'NO_DATA_AVAILABLE' | translate}}</div>
|
|
||||||
</clr-dg-placeholder>
|
</clr-dg-placeholder>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'no'" class="mac-col mac-col--no">
|
<clr-dg-column [clrDgField]="'no'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
NO
|
||||||
<span class="mac-col-title">
|
</ng-container></clr-dg-column>
|
||||||
<clr-icon shape="hashtag"></clr-icon> {{'NO' | translate}}
|
<clr-dg-column [clrDgField]="'menuname'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</span>
|
Menu Item Name
|
||||||
</ng-container>
|
</ng-container></clr-dg-column>
|
||||||
</clr-dg-column>
|
<clr-dg-column [clrDgField]="'mvisible'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-dg-column [clrDgField]="'menuname'" class="mac-col mac-col--name">
|
View
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
</ng-container></clr-dg-column>
|
||||||
<span class="mac-col-title">
|
<clr-dg-column [clrDgField]="'mcreate'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-icon shape="tag"></clr-icon> {{'MENU_ITEM_NAME' | translate}}
|
Create
|
||||||
</span>
|
</ng-container></clr-dg-column>
|
||||||
</ng-container>
|
<clr-dg-column [clrDgField]="'medit'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-column>
|
Edit
|
||||||
<clr-dg-column [clrDgField]="'mvisible'" class="mac-col mac-col--perm">
|
</ng-container></clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'mdelete'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mac-col-title">
|
Delete
|
||||||
<clr-icon shape="eye"></clr-icon> {{'VIEW' | translate}}
|
<!-- <input type="checkbox" clrCheckbox name="mdelete" [checked]="colvalue" (change)="changedelete($event.target.checked)"> -->
|
||||||
</span>
|
</ng-container></clr-dg-column>
|
||||||
</ng-container>
|
<clr-dg-column [clrDgField]="'mquery'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-column>
|
Query
|
||||||
<clr-dg-column [clrDgField]="'mcreate'" class="mac-col mac-col--perm">
|
</ng-container></clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'mexport'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mac-col-title">
|
Export
|
||||||
<clr-icon shape="plus"></clr-icon> {{'CREATE' | translate}}
|
</ng-container></clr-dg-column>
|
||||||
</span>
|
<ng-container *ngFor="let all of alldata;let i=index">
|
||||||
</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-row *ngIf="all.menuId==0">
|
<clr-dg-row *ngIf="all.menuId==0">
|
||||||
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell><b>{{all.menuItemDesc}}</b></clr-dg-cell>
|
||||||
<b>{{all.menuItemDesc}}</b>
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mvisible"></clr-dg-cell>
|
||||||
</clr-dg-cell>
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mcreate"></clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell> <input type="checkbox" [checked]="selected ===all.medit"></clr-dg-cell>
|
||||||
<input type="checkbox" [checked]="selected ===all.mvisible">
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mdelete"></clr-dg-cell>
|
||||||
</clr-dg-cell>
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mquery"></clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell><input type="checkbox" [checked]="selected ===all.mexport"></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>
|
<clr-dg-action-overflow>
|
||||||
<button class="mac-action-item" (click)="Sync(all.menuItemId?.menuItemId,all)">
|
<button class="action-item" (click)="Sync(all.menuItemId?.menuItemId,all)">Sync <clr-icon shape="shrink"
|
||||||
{{'SYNC' | translate}}
|
class="is-error"></clr-icon> </button>
|
||||||
<clr-icon shape="shrink" class="is-error"></clr-icon>
|
|
||||||
</button>
|
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-title">{{'MENUNAME' | translate}}</td>
|
<td class="td-title">Menuname</td>
|
||||||
<td class="td-content"></td>
|
<td class="td-content"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</clr-dg-row-detail>
|
</clr-dg-row-detail>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<clr-dg-footer class="mac-grid-footer">
|
<clr-dg-footer>
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{'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}}
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
{{'OF' | translate}} {{pagination.totalItems}} {{'RECORDS' | translate}}
|
of {{pagination.totalItems}} Records
|
||||||
</clr-dg-pagination>
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
|
|
||||||
<clr-modal class="mac-modal" [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
<h3 class="modal-title mac-modal-title">
|
<h3 class="modal-title">Add Menu Access Control</h3>
|
||||||
<clr-icon shape="plus"></clr-icon> {{'ADD_MENU_ACCESS_CONTROL' | translate}}
|
|
||||||
</h3>
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<div class="clr-col-sm-12 mac-form-group">
|
<div class="clr-col-sm-12">
|
||||||
<label class="mac-form-label" for="name">{{'MENU_NAME' | translate}}
|
<label for="name">Menu Name<span class="required-field">*</span></label>
|
||||||
<span class="required-field">*</span>
|
<select name="" id="" style="width: 100%;padding: 5px 5px;border: 1px solid #ccc;border-radius: 4px;"
|
||||||
</label>
|
(change)="idselected($event.target.value)">
|
||||||
<select name="" id="" class="mac-form-select w-100" (change)="idselected($event.target.value)">
|
|
||||||
<option *ngFor="let sub of menus" [value]="sub.menuItemId">{{sub.menuItemDesc}}</option>
|
<option *ngFor="let sub of menus" [value]="sub.menuItemId">{{sub.menuItemDesc}}</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="mac-form-help">{{'CHOOSE_MAIN_MENU_FOR_ACCESS' | translate}}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="mac-btn mac-btn-outline" (click)="modalAdd = false">
|
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
|
||||||
<clr-icon shape="times"></clr-icon>
|
<button type="submit" class="btn btn-primary">ADD</button>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
<clr-modal class="mac-modal" [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
<h3 class="modal-title mac-modal-title">
|
<h3 class="modal-title">Update Menu Access Control</h3>
|
||||||
<clr-icon shape="edit"></clr-icon> {{'UPDATE_MENU_ACCESS_CONTROL' | translate}}
|
|
||||||
</h3>
|
|
||||||
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
||||||
<div class="mac-form-help">{{'MENU_ID' | translate}}:
|
<h2 class="heading">{{rowSelected.menuItemId.menuItemId}}</h2>
|
||||||
<code class="clr-code">{{rowSelected.menuItemId.menuItemId}}</code>
|
|
||||||
</div>
|
|
||||||
<form (ngSubmit)="onUpdate(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)">
|
<form (ngSubmit)="onUpdate(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)">
|
||||||
<div class="mac-perms">
|
<div class="clr-row">
|
||||||
<label class="mac-perm">
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
<input type="checkbox" clrCheckbox name="mvisible" [(ngModel)]="selected ===rowSelected.mvisible" (change)="rowSelected.mvisible === $event.target.checked ? 'y' : 'n'">
|
<label for="tags">View</label>
|
||||||
<span class="mac-perm__icon">
|
<input type="checkbox" clrCheckbox name="mvisible" [(ngModel)]="selected ===rowSelected.mvisible"
|
||||||
<clr-icon shape="eye"></clr-icon>
|
(change)="rowSelected.mvisible === $event.target.checked ? 'y' : 'n'">
|
||||||
</span>
|
</div>
|
||||||
<span class="mac-perm__label">{{'VIEW' | translate}}</span>
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
</label>
|
<label for="name">Create</label>
|
||||||
<label class="mac-perm">
|
<input type="checkbox" clrCheckbox name="mcreate" name="mcreate"
|
||||||
<input type="checkbox" clrCheckbox name="mcreate" [(ngModel)]="selected ===rowSelected.mcreate" (change)="rowSelected.mcreate === $event.target.checked ? 'y' : 'n'">
|
[(ngModel)]="selected ===rowSelected.mcreate"
|
||||||
<span class="mac-perm__icon">
|
(change)="rowSelected.mcreate === $event.target.checked ? 'y' : 'n'">
|
||||||
<clr-icon shape="plus"></clr-icon>
|
</div>
|
||||||
</span>
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
<span class="mac-perm__label">{{'CREATE' | translate}}</span>
|
<label for="tags">Edit</label>
|
||||||
</label>
|
<input type="checkbox" clrCheckbox name="medit" [(ngModel)]="selected ===rowSelected.medit"
|
||||||
<label class="mac-perm">
|
(change)="rowSelected.medit === $event.target.checked ? 'y' : 'n'">
|
||||||
<input type="checkbox" clrCheckbox name="medit" [(ngModel)]="selected ===rowSelected.medit" (change)="rowSelected.medit === $event.target.checked ? 'y' : 'n'">
|
</div>
|
||||||
<span class="mac-perm__icon">
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
<clr-icon shape="edit"></clr-icon>
|
<label for="tags">Delete</label>
|
||||||
</span>
|
<input type="checkbox" clrCheckbox name="mdelete" [(ngModel)]="selected ===rowSelected.mdelete"
|
||||||
<span class="mac-perm__label">{{'EDIT' | translate}}</span>
|
(change)="rowSelected.mdelete === $event.target.checked ? 'y' : 'n'">
|
||||||
</label>
|
</div>
|
||||||
<label class="mac-perm">
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
<input type="checkbox" clrCheckbox name="mdelete" [(ngModel)]="selected ===rowSelected.mdelete" (change)="rowSelected.mdelete === $event.target.checked ? 'y' : 'n'">
|
<label for="tags">Query</label>
|
||||||
<span class="mac-perm__icon">
|
<input type="checkbox" clrCheckbox name="mquery" [(ngModel)]="selected ===rowSelected.mquery"
|
||||||
<clr-icon shape="trash"></clr-icon>
|
(change)="rowSelected.mquery === $event.target.checked ? 'y' : 'n'">
|
||||||
</span>
|
</div>
|
||||||
<span class="mac-perm__label">{{'DELETE' | translate}}</span>
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
</label>
|
<label for="tags">Export</label>
|
||||||
<label class="mac-perm">
|
<input type="checkbox" clrCheckbox name="mexport" [(ngModel)]="selected ===rowSelected.mexport"
|
||||||
<input type="checkbox" clrCheckbox name="mquery" [(ngModel)]="selected ===rowSelected.mquery" (change)="rowSelected.mquery === $event.target.checked ? 'y' : 'n'">
|
(change)="rowSelected.mexport === $event.target.checked ? 'y' : 'n'">
|
||||||
<span class="mac-perm__icon">
|
</div>
|
||||||
<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>
|
</div>
|
||||||
|
<br>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="mac-btn mac-btn-outline" (click)="modaledit = false">
|
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
|
||||||
<clr-icon shape="times"></clr-icon>
|
<button type="submit" class="btn btn-primary">UPDATE</button>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
<clr-modal class="mac-modal" [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
<clr-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 class="mac-modal" [(clrModalOpen)]="modaldelete1" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
||||||
<div class="modal-body">
|
<h1 class="delete">Are You Sure Want to delete?</h1>
|
||||||
<div class="mac-delete-header">
|
<h2 class="heading">{{rowSelected.menuItemId.menuItemId}}</h2>
|
||||||
<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-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="mac-btn mac-btn-outline" (click)="modaldelete1 = false">
|
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
|
||||||
<clr-icon shape="times"></clr-icon>
|
<button type="submit" (click)="delete(rowSelected.menuItemId.menuItemId,rowSelected.usrGrp.usrGrp)"
|
||||||
<span>{{'CANCEL' | translate}}</span>
|
class="btn btn-primary">Delete</button>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
</div>
|
<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>
|
||||||
@@ -1,160 +1,43 @@
|
|||||||
@import '../../../../../styles/_design-tokens.scss';
|
input[type=text],[type=date],[type=password],[type=checkbox] {
|
||||||
|
|
||||||
// Import base styles
|
|
||||||
input[type=text],
|
|
||||||
[type=date],
|
|
||||||
[type=password],
|
|
||||||
[type=checkbox] {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
background-color: var(--theme-surface);
|
// margin: 3px 0;
|
||||||
|
background-color:rgb(255, 255, 255);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid #ccc;
|
||||||
border-radius: 8px;
|
border-radius: 4px;
|
||||||
box-sizing: border-box;
|
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);
|
|
||||||
}
|
}
|
||||||
|
// select{
|
||||||
.required-field {
|
// width: 100%;
|
||||||
color: var(--theme-error, #ef4444);
|
// padding: 5px 5px;
|
||||||
font-size: 18px;
|
// border: 1px solid #ccc;
|
||||||
}
|
// border-radius: 4px;
|
||||||
|
// }
|
||||||
.td-title {
|
.td-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
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;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.td-content{
|
||||||
.td-content {
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
.delete,.heading{
|
||||||
// Modern Button Styles using ThemeService
|
text-align: center;
|
||||||
.mac-btn {
|
color: red;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//toggle button
|
//toggle button
|
||||||
.switch {
|
.switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -193,585 +76,24 @@ th {
|
|||||||
transition: .4s;
|
transition: .4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked+.slider {
|
input:checked + .slider {
|
||||||
background-color: var(--theme-primary);
|
background-color: #0072a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus+.slider {
|
input:focus + .slider {
|
||||||
box-shadow: 0 0 1px var(--theme-primary);
|
box-shadow: 0 0 1px #0072a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked+.slider:before {
|
input:checked + .slider:before {
|
||||||
-webkit-transform: translateX(26px);
|
-webkit-transform: translateX(26px);
|
||||||
-ms-transform: translateX(26px);
|
-ms-transform: translateX(26px);
|
||||||
transform: translateX(26px);
|
transform: translateX(26px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider.round {
|
.slider.round {
|
||||||
border-radius: 34px;
|
border-radius: 34px;
|
||||||
}
|
|
||||||
|
|
||||||
.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) {
|
.slider.round:before {
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,6 @@ import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
|
|||||||
import { MenumaintanceService } from 'src/app/services/admin/menumaintance.service';
|
import { MenumaintanceService } from 'src/app/services/admin/menumaintance.service';
|
||||||
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
||||||
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
|
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
|
||||||
import { ThemeService } from 'src/app/services/theme.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-menuaccesscontrol',
|
selector: 'app-menuaccesscontrol',
|
||||||
templateUrl: './menuaccesscontrol.component.html',
|
templateUrl: './menuaccesscontrol.component.html',
|
||||||
@@ -38,26 +36,15 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
toggle: boolean = false;
|
toggle: boolean = false;
|
||||||
maindata;
|
maindata;
|
||||||
showdata;
|
showdata;
|
||||||
// UI enhancements (no API changes)
|
constructor(private mainservice: UsermaintanceService,
|
||||||
filterText: string = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private mainservice: UsermaintanceService,
|
|
||||||
private _fb: FormBuilder,
|
private _fb: FormBuilder,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private usergrpservice: UsergrpmaintainceService,
|
private usergrpservice: UsergrpmaintainceService,
|
||||||
private menuGroupService: MenuGroupService,
|
private menuGroupService: MenuGroupService,
|
||||||
private menuservice: MenumaintanceService,
|
private menuservice: MenumaintanceService,) { }
|
||||||
private themeService: ThemeService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
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();
|
this.showdata = this.menuGroupService.getdata();
|
||||||
console.log(this.showdata);
|
console.log(this.showdata);
|
||||||
this.mcreate = this.showdata.mcreate;
|
this.mcreate = this.showdata.mcreate;
|
||||||
@@ -74,64 +61,35 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
grpid: this.usergrpid,
|
grpid: this.usergrpid,
|
||||||
gmenuid: this.menuselectid,
|
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() {
|
dropddowngetdata() {
|
||||||
this.usergrpservice.getAll().subscribe((data) => {
|
this.usergrpservice.getAll().subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.givendata = data;
|
this.givendata = data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getdata() {
|
getdata() {
|
||||||
this.menuservice.getByCurrentUserMenuGroupId1().subscribe(resp => {
|
this.menuservice.getByCurrentUserMenuGroupId1().subscribe(resp => {
|
||||||
this.menus = resp;
|
this.menus = resp;
|
||||||
console.log('menus: ', this.menus);
|
console.log('menus: ', this.menus);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getall() {
|
getall() {
|
||||||
this.usergrpservice.getall().subscribe((data) => {
|
this.usergrpservice.getall().subscribe((data) => {
|
||||||
this.secmenuaccessdata = data
|
this.secmenuaccessdata = data
|
||||||
console.log(data);
|
console.log(data);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
idofselected(val) {
|
idofselected(val) {
|
||||||
console.log(val);
|
console.log(val);
|
||||||
this.usergrpid = val;
|
this.usergrpid = val;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
idselected(val) {
|
idselected(val) {
|
||||||
console.log(val)
|
console.log(val)
|
||||||
this.menuselectid = val;
|
this.menuselectid = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
getbyuseriddata() {
|
getbyuseriddata() {
|
||||||
this.usergrpservice.getbyusergrpid(this.usergrpid).subscribe((data) => {
|
this.usergrpservice.getbyusergrpid(this.usergrpid).subscribe((data) => {
|
||||||
this.alldata = data;
|
this.alldata = data;
|
||||||
@@ -146,19 +104,17 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
|
|
||||||
// console.log(this.menudata)
|
// console.log(this.menudata)
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.log('Error in adding data...', error);
|
console.log('Error in adding data...', error);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.error = "No data Available"
|
this.error = "No data Available"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
modaladd() {
|
modaladd() {
|
||||||
this.modalAdd = true;
|
this.modalAdd = true;
|
||||||
this.getdata();
|
this.getdata();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.modalAdd = false;
|
this.modalAdd = false;
|
||||||
this.entryForm.value.grpid = this.usergrpid;
|
this.entryForm.value.grpid = this.usergrpid;
|
||||||
@@ -172,30 +128,28 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
|
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.log('Error in adding data...', error);
|
console.log('Error in adding data...', error);
|
||||||
if (error.status == 400) {
|
if (error.status == 400) {
|
||||||
this.toastr.error(error.error);
|
this.toastr.error(error.error);
|
||||||
|
|
||||||
|
} else if (error) {
|
||||||
|
this.toastr.error('Not added Data Getting Some Error');
|
||||||
|
}
|
||||||
|
this.ngOnInit();
|
||||||
|
});
|
||||||
|
|
||||||
} else if (error) {
|
|
||||||
this.toastr.error('Not added Data Getting Some Error');
|
|
||||||
}
|
|
||||||
this.ngOnInit();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modalEdit(row) {
|
modalEdit(row) {
|
||||||
this.rowSelected = row;
|
this.rowSelected = row;
|
||||||
console.log(this.rowSelected);
|
console.log(this.rowSelected);
|
||||||
this.modaledit = true;
|
this.modaledit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
modalDelete(row) {
|
modalDelete(row) {
|
||||||
this.rowSelected = row;
|
this.rowSelected = row;
|
||||||
console.log(this.rowSelected)
|
console.log(this.rowSelected)
|
||||||
this.modaldelete = true;
|
this.modaldelete = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(id, usrgrp) {
|
delete(id, usrgrp) {
|
||||||
this.modaldelete = false;
|
this.modaldelete = false;
|
||||||
this.usergrpservice.delete(id, usrgrp).subscribe((data) => {
|
this.usergrpservice.delete(id, usrgrp).subscribe((data) => {
|
||||||
@@ -212,13 +166,11 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
modaldeletemainmenu(row) {
|
modaldeletemainmenu(row) {
|
||||||
this.rowSelected = row;
|
this.rowSelected = row;
|
||||||
console.log(this.rowSelected)
|
console.log(this.rowSelected)
|
||||||
this.modaldelete1 = true;
|
this.modaldelete1 = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete1(id, usrgrp) {
|
delete1(id, usrgrp) {
|
||||||
this.modaldelete1 = false;
|
this.modaldelete1 = false;
|
||||||
this.usergrpservice.deletemain(id, usrgrp).subscribe((data) => {
|
this.usergrpservice.deletemain(id, usrgrp).subscribe((data) => {
|
||||||
@@ -235,7 +187,6 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(id: any, usrgrp: any) {
|
onUpdate(id: any, usrgrp: any) {
|
||||||
this.modaledit = false;
|
this.modaledit = false;
|
||||||
console.log(id, usrgrp);
|
console.log(id, usrgrp);
|
||||||
@@ -253,7 +204,6 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onChecked(value) {
|
onChecked(value) {
|
||||||
if (value == "y") {
|
if (value == "y") {
|
||||||
this.selected = "y"
|
this.selected = "y"
|
||||||
@@ -263,13 +213,11 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
this.selected = "n"// make a call for unchecked
|
this.selected = "n"// make a call for unchecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changedelete(val) {
|
changedelete(val) {
|
||||||
console.log(val);
|
console.log(val);
|
||||||
val = this.colvalue = val;
|
val = this.colvalue = val;
|
||||||
console.log(val);
|
console.log(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
data: {};
|
data: {};
|
||||||
Sync(id: any, row) {
|
Sync(id: any, row) {
|
||||||
this.rowSelected = row;
|
this.rowSelected = row;
|
||||||
@@ -291,9 +239,8 @@ export class MenuaccesscontrolComponent implements OnInit {
|
|||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCheckbox() {
|
toggleCheckbox() {
|
||||||
this.toggle = !this.toggle;
|
this.toggle = !this.toggle;
|
||||||
//this.dataService.setDivToggler(this.toggler);
|
//this.dataService.setDivToggler(this.toggler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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://" [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://"><clr-icon shape="lock"></clr-icon>{{ 'SECURITY' | translate }}</a></li>
|
||||||
<li><a href="javascript://">{{ 'MENU_MAINTENANCE' | translate }}</a></li>
|
<li><a href="javascript://">{{ 'MENU_MAINTENANCE' | translate }}</a></li>
|
||||||
</ol>
|
</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-row">
|
||||||
<div class="clr-col-12" style="text-align: right;">
|
<div class="clr-col-12" style="text-align: right;">
|
||||||
<button id="add" *ngIf="mcreate == 'true'" class="btn btn-primary" (click)="goToAdd()">
|
<button id="add" *ngIf="mcreate == 'true'" class="btn btn-primary" (click)="goToAdd()">
|
||||||
@@ -41,44 +13,29 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="mm-stats">
|
<div class="clr-row" style="margin-top: 15px;">
|
||||||
<div class="mm-stat">
|
<div class="clr-col">
|
||||||
<div class="mm-stat__value">{{ totalMenus }}</div>
|
<h3 style="font-weight: 300;display: inline;">{{ 'MENU_MAINTENANCE' | translate }} </h3>
|
||||||
<div class="mm-stat__label">{{ 'TOTAL_MENUS' | translate }}</div>
|
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">{{ 'MAIN_MENU' | translate }}</span><br>
|
||||||
</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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mm-toolbar__right">
|
|
||||||
<select class="mm-select" [(ngModel)]="statusFilter">
|
<div class="clr-col" style="text-align: right;">
|
||||||
<option value="All">{{ 'ALL' | translate }}</option>
|
<div class="btn-group">
|
||||||
<option value="Enable">{{ 'ENABLED' | translate }}</option>
|
<button class="btn text-dark" (click)="downloadFiles()"><b>{{ 'DOWNLOAD_TEMPLATE' | translate }}</b></button>
|
||||||
<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>
|
|
||||||
<button class="btn btn-sm" [class.btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
|
|
||||||
<clr-icon shape="table"></clr-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</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" (click)="goToAdd()">
|
||||||
|
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- <table class="table">
|
<!-- <table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -187,253 +144,173 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table> -->
|
</table> -->
|
||||||
|
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected">
|
||||||
<div class="mm-skeleton-list" *ngIf="loading">
|
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{'LOADING'|translate}} ... </clr-spinner></ng-template>
|
||||||
<div class="mm-skeleton-item" *ngFor="let i of [1,2,3,4,5,6]">
|
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
|
||||||
<div class="mm-skel mm-skel--icon"></div>
|
<clr-dg-column [clrDgField]="''" style="max-width: 40px;">
|
||||||
<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">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}"></ng-container>
|
<ng-container *clrDgHideableColumn="{hidden: false}"></ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--no">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mm-col-title"><clr-icon shape="hashtag"></clr-icon> {{'NO' | translate}}</span>
|
{{'NO' | translate}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--name">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<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>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--id">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mm-col-title"><clr-icon shape="id-badge"></clr-icon> {{'ID' | translate}}</span>
|
{{'ID' | translate}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--seq">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mm-col-title"><clr-icon shape="number-list"></clr-icon> {{'SEQUENCE' | translate}}</span>
|
{{'SEQUENCE' | translate}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--module">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<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>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--link">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<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>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--status">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<span class="mm-col-title"><clr-icon shape="shield-check"></clr-icon> {{'STATUS' | translate}}</span>
|
{{'STATUS' | translate}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--submenu">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
{{'SUB_MENU' | translate}}
|
{{'SUB_MENU' | translate}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column class="mm-col mm-col--action">
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-icon shape="bars"></clr-icon>
|
<clr-icon shape="bars"></clr-icon>
|
||||||
{{'ACTION' | translate}}
|
{{'ACTION' | translate}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
|
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let user of menus;let i = index" [clrDgItem]="user">
|
||||||
<clr-dg-row class="mm-row" *clrDgItems="let user of filteredMenus;let i = index" [clrDgItem]="user">
|
<clr-dg-cell style="max-width: 40px;">
|
||||||
<clr-dg-cell class="mm-cell mm-cell--icon">
|
<span style="cursor: pointer;"><clr-icon shape="edit" (click)="goToEdit(user)" class="red is-error" style="color:red;"></clr-icon></span>
|
||||||
<span class="mm-icon-action"><clr-icon shape="edit" (click)="goToEdit(user)"
|
</clr-dg-cell>
|
||||||
class="is-error mm-icon-edit"></clr-icon></span>
|
<clr-dg-cell >{{i+1}}</clr-dg-cell>
|
||||||
</clr-dg-cell>
|
<clr-dg-cell>{{user.menuItemDesc}}</clr-dg-cell>
|
||||||
<clr-dg-cell class="mm-cell mm-cell--no">{{i+1}}</clr-dg-cell>
|
<clr-dg-cell >{{user.menuId}}</clr-dg-cell>
|
||||||
<clr-dg-cell class="mm-cell mm-cell--name">
|
<clr-dg-cell >{{user.itemSeq}}</clr-dg-cell>
|
||||||
<div class="mm-item">
|
<clr-dg-cell > {{user.moduleName}} </clr-dg-cell>
|
||||||
<div class="mm-item__avatar"><clr-icon shape="bookmark"></clr-icon></div>
|
<clr-dg-cell > {{user.main_menu_action_name}}</clr-dg-cell>
|
||||||
<div class="mm-item__text">
|
<clr-dg-cell >{{user.status}} </clr-dg-cell>
|
||||||
<div class="mm-item__title">{{user.menuItemDesc}}</div>
|
<clr-dg-cell ><clr-icon shape="bullet-list" (click)="submenu(user.menuItemId)" style="color: rgb(108, 108, 194);"></clr-icon> </clr-dg-cell>
|
||||||
<div class="mm-item__meta">ID: {{user.menuId}} ⢠Seq: {{user.itemSeq}}</div>
|
<clr-dg-cell>
|
||||||
</div>
|
<span style="cursor: pointer;padding: 10px; "><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error" style="color: red;"></clr-icon></span>
|
||||||
</div>
|
<clr-signpost>
|
||||||
</clr-dg-cell>
|
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span>
|
||||||
<clr-dg-cell class="mm-cell mm-cell--id">{{user.menuId}}</clr-dg-cell>
|
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
||||||
<clr-dg-cell class="mm-cell mm-cell--seq">{{user.itemSeq}}</clr-dg-cell>
|
<h5 style="margin-top: 0">{{'WHO_COLUMN'|translate}} </h5>
|
||||||
<clr-dg-cell class="mm-cell mm-cell--module">
|
<div>{{'ACCOUNT_ID'|translate}} <code class="clr-code">{{user.accountId}}</code></div>
|
||||||
<div class="mm-chip mm-chip--module">
|
<div>{{'CREATED_AT'|translate}} <code class="clr-code">{{user.createdAt| date}}</code></div>
|
||||||
<clr-icon shape="application"></clr-icon>
|
<div>{{'CREATED_BY'|translate}} <code class="clr-code">{{user.createdBy}}</code></div>
|
||||||
<span>{{user.moduleName}}</span>
|
<div>{{'UPDATED_AT'|translate}} <code class="clr-code">{{user.updatedAt | date}}</code></div>
|
||||||
</div>
|
<div>{{'UPDATED_BY'|translate}} <code class="clr-code">{{user.updatedBy}}</code></div>
|
||||||
</clr-dg-cell>
|
</clr-signpost-content>
|
||||||
<clr-dg-cell class="mm-cell mm-cell--link">
|
</clr-signpost>
|
||||||
<div class="mm-link">
|
</clr-dg-cell>
|
||||||
<clr-icon shape="link"></clr-icon>
|
<!-- <clr-dg-action-overflow>
|
||||||
<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-signpost>
|
|
||||||
<span class="mm-icon-action" clrSignpostTrigger><clr-icon shape="help"
|
|
||||||
class="mm-icon-help"></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>
|
|
||||||
<div>{{'CREATED_AT'|translate}} <code class="clr-code">{{user.createdAt| date}}</code></div>
|
|
||||||
<div>{{'CREATED_BY'|translate}} <code class="clr-code">{{user.createdBy}}</code></div>
|
|
||||||
<div>{{'UPDATED_AT'|translate}} <code class="clr-code">{{user.updatedAt | date}}</code></div>
|
|
||||||
<div>{{'UPDATED_BY'|translate}} <code class="clr-code">{{user.updatedBy}}</code></div>
|
|
||||||
</clr-signpost-content>
|
|
||||||
</clr-signpost>
|
|
||||||
</clr-dg-cell>
|
|
||||||
<!-- <clr-dg-action-overflow>
|
|
||||||
<button class="action-item" *ngIf="medit == 'true'" (click)="goToEdit(user)">Edit <clr-icon shape="edit" class="is-error"></clr-icon></button>
|
<button class="action-item" *ngIf="medit == 'true'" (click)="goToEdit(user)">Edit <clr-icon shape="edit" class="is-error"></clr-icon></button>
|
||||||
<button class="action-item" *ngIf="mdelete == 'true'" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button>
|
<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-action-overflow> -->
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-row-detail *clrIfExpanded >
|
||||||
<div class="mm-detail">
|
<table class="table">
|
||||||
<div class="mm-detail__row"><span>Menu Name</span><strong>{{user.menuItemDesc}}</strong></div>
|
<tr>
|
||||||
<div class="mm-detail__row"><span>Action Link</span><strong>{{user.main_menu_action_name}}</strong></div>
|
<td class="td-title">Menu Name: </td>
|
||||||
<div class="mm-detail__row"><span>Module</span><strong>{{user.moduleName}}</strong></div>
|
<td class="td-content">{{user.menuItemDesc}}</td>
|
||||||
</div>
|
</tr>
|
||||||
</clr-dg-row-detail>
|
</table>
|
||||||
</clr-dg-row>
|
</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-pagination #pagination [clrDgPageSize]="10">
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
of {{pagination.totalItems}} users
|
of {{pagination.totalItems}} users
|
||||||
</clr-dg-pagination>
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="viewMode==='cards'">
|
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
<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">
|
|
||||||
<h3 class="modal-title">Add Menu Maintenance</h3>
|
<h3 class="modal-title">Add Menu Maintenance</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<!-- <div class="clr-col-sm-12">
|
<!-- <div class="clr-col-sm-12">
|
||||||
<label for="name">Menu Item ID<span class="required-field">*</span></label>
|
<label for="name">Menu Item ID<span class="required-field">*</span></label>
|
||||||
<input type="text" class="clr-input" formControlName="menuItemId">
|
<input type="text" class="clr-input" formControlName="menuItemId">
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="name">Menu ID<span class="required-field">*</span></label>
|
<label for="name">Menu ID<span class="required-field">*</span></label>
|
||||||
<input type="text" class="clr-input" formControlName="menuId" name="menuId" [(ngModel)]="mainid" readonly>
|
<input type="text" class="clr-input" formControlName="menuId" name="menuId" [(ngModel)]="mainid" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="name">Menu Item Name<span class="required-field">*</span></label>
|
<label for="name">Menu Item Name<span class="required-field">*</span></label>
|
||||||
<input type="text" class="clr-input" formControlName="menuItemDesc">
|
<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" class="error_mess">
|
||||||
<div *ngIf="submitted && entryForm.controls.menuItemDesc.errors.required" class="error_mess">*This field
|
<div *ngIf="submitted && entryForm.controls.menuItemDesc.errors.required" class="error_mess">*This field is Required</div>
|
||||||
is Required</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Sequence</label>
|
<label for="tags">Sequence</label>
|
||||||
<input type="text" class="clr-input" formControlName="itemSeq">
|
<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" class="error_mess">
|
||||||
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.required" class="error_mess">*This field is
|
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.required" class="error_mess">*This field is Required</div>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div *ngIf="submitted && entryForm.controls.itemSeq.errors" class="error_mess">
|
||||||
<label for="tags">Module Name</label>
|
<div *ngIf="submitted && entryForm.controls.itemSeq.errors.pattern" class="error_mess">*Required Only Number</div>
|
||||||
<input type="text" class="clr-input" formControlName="moduleName">
|
</div>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
<div class="clr-col-sm-12">
|
||||||
<div class="clr-col-sm-12">
|
<label for="tags">Module Name</label>
|
||||||
<label for="tags">Status</label>
|
<input type="text" class="clr-input" formControlName="moduleName">
|
||||||
<select id="" formControlName="status">
|
<div *ngIf="submitted && entryForm.controls.moduleName.errors" class="error_mess">
|
||||||
<option value="Enable">Enable</option>
|
<div *ngIf="submitted && entryForm.controls.moduleName.errors.required" class="error_mess">*This field is Required</div>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Menu Action Link</label>
|
<label for="tags">Status</label>
|
||||||
<input type="text" class="clr-input" formControlName="main_menu_action_name">
|
<select id="" formControlName="status">
|
||||||
<div *ngIf="submitted && entryForm.controls.main_menu_action_name.errors" class="error_mess">
|
<option value="Enable">Enable</option>
|
||||||
<div *ngIf="submitted && entryForm.controls.main_menu_action_name.errors.required" class="error_mess">
|
<option value="Disable">Disable</option>
|
||||||
*This field is Required</div>
|
</select>
|
||||||
</div>
|
<div *ngIf="submitted && entryForm.controls.status.errors" class="error_mess">
|
||||||
<!-- <select NAME="menu_id" >
|
<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>
|
||||||
|
<!-- <select NAME="menu_id" >
|
||||||
<option SELECTED >Home Menu
|
<option SELECTED >Home Menu
|
||||||
<OPTION >Home
|
<OPTION >Home
|
||||||
<OPTION >Job
|
<OPTION >Job
|
||||||
@@ -471,99 +348,94 @@
|
|||||||
|
|
||||||
|
|
||||||
</select> -->
|
</select> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Menu Icon Name</label>
|
<label for="tags">Menu Icon Name</label>
|
||||||
<input type="text" class="clr-input" formControlName="main_menu_icon_name">
|
<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" class="error_mess">
|
||||||
<div *ngIf="submitted && entryForm.controls.main_menu_icon_name.errors.required" class="error_mess">*This
|
<div *ngIf="submitted && entryForm.controls.main_menu_icon_name.errors.required" class="error_mess">*This field is Required</div>
|
||||||
field is Required</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</clr-modal>
|
</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">
|
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
||||||
<h1 class="delete">Are You Sure Want to delete?</h1>
|
<h1 class="delete">Are You Sure Want to delete?</h1>
|
||||||
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
|
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</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>
|
<h3 class="modal-title">Update Menu Maintenance</h3>
|
||||||
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
||||||
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
|
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
|
||||||
<form (ngSubmit)="onUpdate(rowSelected.menuItemId)">
|
<form (ngSubmit)="onUpdate(rowSelected.menuItemId)">
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
|
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="name">Menu ID<span class="required-field">*</span></label>
|
<label for="name">Menu ID<span class="required-field">*</span></label>
|
||||||
<input type="text" class="clr-input" name="menuId" [(ngModel)]="rowSelected.menuId" readonly>
|
<input type="text" class="clr-input" name="menuId" [(ngModel)]="rowSelected.menuId" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="name">Menu Item Name<span class="required-field">*</span></label>
|
<label for="name">Menu Item Name<span class="required-field">*</span></label>
|
||||||
<input type="text" class="clr-input" name="menuItemDesc" [(ngModel)]="rowSelected.menuItemDesc">
|
<input type="text" class="clr-input" name="menuItemDesc" [(ngModel)]="rowSelected.menuItemDesc">
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Sequence</label>
|
<label for="tags">Sequence</label>
|
||||||
<input type="text" class="clr-input" name="itemSeq" [(ngModel)]="rowSelected.itemSeq">
|
<input type="text" class="clr-input" name="itemSeq" [(ngModel)]="rowSelected.itemSeq">
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Module Name</label>
|
<label for="tags">Module Name</label>
|
||||||
<input type="text" class="clr-input" name="moduleName" [(ngModel)]="rowSelected.moduleName">
|
<input type="text" class="clr-input" name="moduleName" [(ngModel)]="rowSelected.moduleName">
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Status</label>
|
<label for="tags">Status</label>
|
||||||
<select id="" name="status" [(ngModel)]="rowSelected.status">
|
<select id="" name="status" [(ngModel)]="rowSelected.status">
|
||||||
<option value="Enable">Enable</option>
|
<option value="Enable">Enable</option>
|
||||||
<option value="Disable">Disable</option>
|
<option value="Disable">Disable</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
<label for="tags">Menu Action Link</label>
|
<label for="tags">Menu Action Link</label>
|
||||||
<input type="text" class="clr-input" name="main_menu_action_name"
|
<input type="text" class="clr-input" name="main_menu_action_name" [(ngModel)]="rowSelected.main_menu_action_name">
|
||||||
[(ngModel)]="rowSelected.main_menu_action_name">
|
</div>
|
||||||
</div>
|
<div class="clr-col-sm-12">
|
||||||
<div class="clr-col-sm-12">
|
<label for="tags">Menu Icon Name </label>
|
||||||
<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"
|
</div>
|
||||||
[(ngModel)]="rowSelected.main_menu_icon_name">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</clr-modal>
|
</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>
|
<h3 class="modal-title">Import File</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="file" name="file" class="file" (change)="selectFile($event)"
|
<input type="file" name="file" class="file" (change)="selectFile($event)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
|
||||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
|
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
</div>
|
|
||||||
@@ -60,591 +60,3 @@ select{
|
|||||||
border-radius: 4px;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { CsvService } from 'src/app/services/csv.service';
|
|||||||
import { ExcelService } from 'src/app/services/excel.service';
|
import { ExcelService } from 'src/app/services/excel.service';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { ThemeService } from 'src/app/services/theme.service';
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-menumaintance',
|
selector: 'app-menumaintance',
|
||||||
templateUrl: './menumaintance.component.html',
|
templateUrl: './menumaintance.component.html',
|
||||||
@@ -32,10 +31,6 @@ export class MenumaintanceComponent implements OnInit {
|
|||||||
showdata;
|
showdata;
|
||||||
error;
|
error;
|
||||||
submitted = false;
|
submitted = false;
|
||||||
// Quick filter UI state (no API/logic changes)
|
|
||||||
filterText = '';
|
|
||||||
statusFilter: 'All' | 'Enable' | 'Disable' | '' = 'All';
|
|
||||||
viewMode: 'cards' | 'table' = 'cards';
|
|
||||||
constructor(private menuservice: MenumaintanceService,
|
constructor(private menuservice: MenumaintanceService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private excel: ExcelService,
|
private excel: ExcelService,
|
||||||
@@ -44,14 +39,9 @@ export class MenumaintanceComponent implements OnInit {
|
|||||||
private menuGroupService: MenuGroupService,
|
private menuGroupService: MenuGroupService,
|
||||||
private csvService: CsvService,
|
private csvService: CsvService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private router: Router,
|
private router: Router,) { }
|
||||||
private themeService: ThemeService) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
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();
|
this.showdata = this.menuGroupService.getdata();
|
||||||
console.log(this.showdata);
|
console.log(this.showdata);
|
||||||
this.mcreate = this.showdata.mcreate;
|
this.mcreate = this.showdata.mcreate;
|
||||||
@@ -79,71 +69,6 @@ export class MenumaintanceComponent implements OnInit {
|
|||||||
this.getdata();
|
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) {
|
switchLanguage(language: string) {
|
||||||
this.translate.use(language);
|
this.translate.use(language);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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://" [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://"><clr-icon shape="lock"></clr-icon>Security</a></li>
|
||||||
<li><a href="javascript://">Menu Maintenance</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> -->
|
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Sub Menu</span><br> -->
|
||||||
|
|
||||||
|
|
||||||
<section class="mm-hero">
|
<div class="clr-row" style="margin-top: 15px;">
|
||||||
<div class="mm-hero__content">
|
<div class="clr-col-8">
|
||||||
<div class="mm-hero__icon"><clr-icon shape="organization"></clr-icon></div>
|
<h3 style="font-weight: 300;display: inline;">Sub-Menu Maintenance </h3>
|
||||||
<div>
|
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Sub Menu</span><br>
|
||||||
<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="mm-toolbar">
|
</div>
|
||||||
<div class="mm-toolbar__left">
|
<div class="clr-col-4" style="text-align: right;">
|
||||||
<div class="mm-search">
|
<button id="add" class="btn btn-primary" (click)="goToAdd()">
|
||||||
<clr-icon shape="search"></clr-icon>
|
<clr-icon shape="plus"></clr-icon>ADD New
|
||||||
<input class="mm-search__input" type="text" placeholder="Search" [(ngModel)]="filterText" />
|
</button>
|
||||||
</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>
|
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mm-card" *ngIf="viewMode==='table'">
|
<clr-datagrid [clrDgLoading]="loading" >
|
||||||
<clr-datagrid class="mm-grid mm-grid--modern" [clrDgLoading]="loading">
|
|
||||||
<clr-dg-placeholder><clr-spinner [clrMedium]="true">Loading ...</clr-spinner></clr-dg-placeholder>
|
<clr-dg-placeholder><clr-spinner [clrMedium]="true">Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
|
|
||||||
<clr-dg-column > <ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column > <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
@@ -77,29 +49,19 @@
|
|||||||
Status
|
Status
|
||||||
</ng-container></clr-dg-column>
|
</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 >{{i+1}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>{{user.menuItemDesc}}</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.menuId}}</clr-dg-cell>
|
<clr-dg-cell >{{user.menuId}}</clr-dg-cell>
|
||||||
<clr-dg-cell >{{user.itemSeq}}</clr-dg-cell>
|
<clr-dg-cell >{{user.itemSeq}}</clr-dg-cell>
|
||||||
<clr-dg-cell > {{user.moduleName}} </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.main_menu_action_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell >{{user.status}} </clr-dg-cell>
|
<clr-dg-cell >{{user.status}} </clr-dg-cell>
|
||||||
|
|
||||||
<clr-dg-cell class="mm-cell mm-cell--action">
|
<clr-dg-action-overflow>
|
||||||
<div class="mm-actions-inline">
|
<button class="action-item" (click)="goToEdit(user)">Edit <clr-icon shape="edit" class="is-error"></clr-icon></button>
|
||||||
<button class="btn btn-sm btn-outline mm-btn-edit" (click)="goToEdit(user)"><clr-icon shape="edit"></clr-icon></button>
|
<button class="action-item" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button>
|
||||||
<button class="btn btn-sm btn-outline mm-btn-delete" (click)="onDelete(user)"><clr-icon shape="trash"></clr-icon></button>
|
</clr-dg-action-overflow>
|
||||||
</div>
|
|
||||||
</clr-dg-cell>
|
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded >
|
<clr-dg-row-detail *clrIfExpanded >
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@@ -111,7 +73,7 @@
|
|||||||
</clr-dg-row-detail>
|
</clr-dg-row-detail>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
|
|
||||||
<clr-dg-footer class="mm-grid-footer">
|
<clr-dg-footer>
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
@@ -119,31 +81,8 @@
|
|||||||
</clr-dg-pagination>
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="viewMode==='cards'">
|
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
<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">
|
|
||||||
<h3 class="modal-title">Add Sub Menu Maintenance</h3>
|
<h3 class="modal-title">Add Sub Menu Maintenance</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
||||||
@@ -242,7 +181,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,7 +201,7 @@
|
|||||||
</clr-modal>
|
</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>
|
<h3 class="modal-title">Update Sub Menu Maintenance</h3>
|
||||||
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
<div class="modal-body" *ngIf="rowSelected.menuItemId">
|
||||||
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
|
<h2 class="heading">{{rowSelected.menuItemId}}</h2>
|
||||||
@@ -301,7 +240,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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] {
|
input[type=text],[type=date],[type=password],[type=number] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ThemeService } from 'src/app/services/theme.service';
|
|
||||||
import { MenumaintanceService } from '../../../../services/admin/menumaintance.service';
|
import { MenumaintanceService } from '../../../../services/admin/menumaintance.service';
|
||||||
import { Rn_Main_Menu } from '../../../../models/builder/Rn_Main_Menu';
|
import { Rn_Main_Menu } from '../../../../models/builder/Rn_Main_Menu';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
@@ -31,22 +30,13 @@ export class SubmenuComponent implements OnInit {
|
|||||||
mainid;
|
mainid;
|
||||||
submitted = false;
|
submitted = false;
|
||||||
public entryForm: FormGroup;
|
public entryForm: FormGroup;
|
||||||
// UI filter/view state
|
|
||||||
filterText = '';
|
|
||||||
statusFilter: 'All' | 'Enable' | 'Disable' | '' = 'All';
|
|
||||||
viewMode: 'cards' | 'table' = 'cards';
|
|
||||||
|
|
||||||
constructor(private menuservice: MenumaintanceService,
|
constructor(private menuservice: MenumaintanceService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private _fb: FormBuilder,
|
private _fb: FormBuilder,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,) { }
|
||||||
private themeService: ThemeService) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.themeService.currentTheme$.subscribe(() => {
|
|
||||||
// Theme CSS variables applied globally
|
|
||||||
});
|
|
||||||
this.id = this.route.snapshot.params["id"];
|
this.id = this.route.snapshot.params["id"];
|
||||||
console.log("project mainmenu with id = ", this.id);
|
console.log("project mainmenu with id = ", this.id);
|
||||||
this.getById(this.id);
|
this.getById(this.id);
|
||||||
@@ -65,34 +55,6 @@ export class SubmenuComponent implements OnInit {
|
|||||||
|
|
||||||
// this.getdata();
|
// 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) {
|
getById(id: any) {
|
||||||
this.menuservice.getbyid(id).subscribe((data) => {
|
this.menuservice.getbyid(id).subscribe((data) => {
|
||||||
this.sub = data;
|
this.sub = data;
|
||||||
|
|||||||
@@ -1,320 +1,224 @@
|
|||||||
<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://" [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://"><clr-icon shape="lock"></clr-icon>{{ 'SECURITY' | translate }}</a></li>
|
||||||
<li><a href="javascript://">{{'USER_GROUP_MAINTENANCE' | translate}}</a></li>
|
<li><a href="javascript://">{{'USER_GROUP_MAINTENANCE' | translate}}</a></li>
|
||||||
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<section class="ug-hero">
|
|
||||||
<div class="ug-hero__content">
|
<div class="dg-wrapper">
|
||||||
<div class="ug-hero__icon"><clr-icon shape="users"></clr-icon></div>
|
<div class="clr-row">
|
||||||
<div>
|
<div class="clr-col">
|
||||||
<h2 class="ug-hero__title">{{'USER_GROUP_MAINTENANCE' | translate}}</h2>
|
<h3>{{'USER_GROUP_MAINTENANCE' | translate}}</h3>
|
||||||
<p class="ug-hero__subtitle">{{ 'SECURITY' | translate }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ug-hero__actions">
|
<div class="clr-col" style="text-align: right;">
|
||||||
<button class="ug-btn ug-btn-secondary" (click)="downloadFiles()">
|
<div class="btn-group">
|
||||||
<clr-icon shape="download"></clr-icon>
|
<button class="btn text-dark" (click)="downloadFiles()"><b>{{ 'DOWNLOAD_TEMPLATE' | translate }}</b></button>
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
<button class="ug-btn ug-btn-sm" [class.ug-btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
|
|
||||||
<clr-icon shape="table"></clr-icon>
|
|
||||||
</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-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}}
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'groupDesc'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
{{'GROUP_DESC' | translate}}
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'groupLevel'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
{{'GROUP_LEVEL' | translate}}
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'status'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
{{'STATUS' | translate}}
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'updateDateFormated'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
{{'UPDATED_DATE' | translate}}
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of filteredGroups;let i = index" [clrDgItem]="user">
|
|
||||||
<clr-dg-cell>{{user.usrGrp}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{user.groupName}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{user.groupDesc}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{user.groupLevel}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>
|
|
||||||
<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.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>
|
|
||||||
</clr-dg-action-overflow>
|
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="td-title">username</td>
|
|
||||||
<td class="td-content">{{user.groupName}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</clr-dg-row-detail>
|
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer class="ug-grid-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>
|
<button class="btn btn-primary" (click)="csvImport()">{{ 'IMPORT' | translate }}</button>
|
||||||
<div class="ug-card-item__body">
|
<button class="btn btn-outline" (click)="onExport()">
|
||||||
<div class="ug-kv"><span>ID</span><strong>{{user.usrGrp}}</strong></div>
|
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
|
||||||
<div class="ug-kv"><span>Desc</span><strong>{{user.groupDesc}}</strong></div>
|
</button>
|
||||||
<div class="ug-kv"><span>Level</span><strong>{{user.groupLevel}}</strong></div>
|
<button id="add" class="btn btn-primary" *ngIf="mcreate == 'true'" (click)="goToAdd()">
|
||||||
<div class="ug-kv"><span>Updated</span><strong>{{user.updateDateFormated}}</strong></div>
|
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
|
||||||
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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-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}}
|
||||||
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'groupDesc'">
|
||||||
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
{{'GROUP_DESC' | translate}}
|
||||||
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'groupLevel'">
|
||||||
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
{{'GROUP_LEVEL' | translate}}
|
||||||
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'status'">
|
||||||
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
{{'STATUS' | translate}}
|
||||||
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'updateDateFormated'">
|
||||||
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
{{'UPDATED_DATE' | translate}}
|
||||||
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
|
|
||||||
|
<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.groupLevel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{user.status}}</clr-dg-cell>
|
||||||
|
<!-- <clr-dg-cell>{{user.usrGrp}}</clr-dg-cell> -->
|
||||||
|
<clr-dg-cell>{{user.updateDateFormated}}</clr-dg-cell>
|
||||||
|
|
||||||
|
<clr-dg-action-overflow>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td class="td-title">username</td>
|
||||||
|
<td class="td-content">{{user.groupName}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
<h3 class="modal-title">Add User Group Maintenance</h3>
|
<h3 class="modal-title">Add User Group Maintenance</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
|
||||||
|
<div class="clr-row">
|
||||||
<div class="ug-form-group">
|
<div class="clr-col-sm-12">
|
||||||
<label for="groupName" class="ug-form-label">User Group<span class="required-field">*</span></label>
|
<label for="name">User Group<span class="required-field">*</span></label>
|
||||||
<input type="text" id="groupName" class="ug-form-input" formControlName="groupName">
|
<input type="text" class="clr-input" formControlName="groupName">
|
||||||
<div *ngIf="submitted && entryForm.controls.groupName.errors" class="ug-form-error">
|
<div *ngIf="submitted && entryForm.controls.groupName.errors" class="error_mess">
|
||||||
<div *ngIf="submitted && entryForm.controls.groupName.errors.required">*This field is Required</div>
|
<div *ngIf="submitted && entryForm.controls.groupName.errors.required" class="error_mess">*This field is Required</div>
|
||||||
</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>
|
</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>
|
|
||||||
<option value="E">Enable</option>
|
|
||||||
<option value="D">Disable</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>
|
</div>
|
||||||
|
<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></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.groupLevel.errors" class="error_mess">
|
||||||
|
<div *ngIf="submitted && entryForm.controls.groupLevel.errors.required" class="error_mess">*This field is Required</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="ug-btn ug-btn-outline" (click)="modalAdd = false">Cancel</button>
|
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
|
||||||
<button type="submit" class="ug-btn ug-btn-primary">ADD</button>
|
<button type="submit" class="btn btn-primary" >ADD</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
<clr-modal [(clrModalOpen)]="modaledit" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
<h3 class="modal-title">Update User Group Maintenance</h3>
|
<h3 class="modal-title">Update User Group Maintenance</h3>
|
||||||
|
|
||||||
|
|
||||||
<div class="modal-body" *ngIf="rowSelected.usrGrp">
|
<div class="modal-body" *ngIf="rowSelected.usrGrp">
|
||||||
|
<h2 class="heading">{{rowSelected.usrGrp}}</h2>
|
||||||
<div class="ug-form-group">
|
<form (ngSubmit)="onUpdate(rowSelected.usrGrp)">
|
||||||
<label for="editUsrGrp" class="ug-form-label">User Group ID</label>
|
<div class="clr-row">
|
||||||
<input type="text" id="editUsrGrp" class="ug-form-input" [(ngModel)]="rowSelected.usrGrp" name="usrGrp" disabled>
|
<div class="clr-col-sm-12">
|
||||||
</div>
|
<label for="name">User Group<span class="required-field">*</span></label>
|
||||||
|
<input type="text" class="clr-input" [(ngModel)]="rowSelected.groupName" name="groupName">
|
||||||
<div class="ug-form-group">
|
</div>
|
||||||
<label for="editGroupName" class="ug-form-label">User Group<span class="required-field">*</span></label>
|
<div class="clr-col-sm-12">
|
||||||
<input type="text" id="editGroupName" class="ug-form-input" [(ngModel)]="rowSelected.groupName" name="groupName">
|
<label for="name">Description<span class="required-field">*</span></label>
|
||||||
</div>
|
<input type="text" class="clr-input" [(ngModel)]="rowSelected.groupDesc" name="groupDesc">
|
||||||
|
</div>
|
||||||
<div class="ug-form-group">
|
<div class="clr-col-sm-12">
|
||||||
<label for="editGroupDesc" class="ug-form-label">Description<span class="required-field">*</span></label>
|
<label for="tags">Status<span class="required-field">*</span></label>
|
||||||
<input type="text" id="editGroupDesc" class="ug-form-input" [(ngModel)]="rowSelected.groupDesc" name="groupDesc">
|
<select id="" [(ngModel)]="rowSelected.status" name="status">
|
||||||
</div>
|
<option value="E">Enable</option>
|
||||||
|
<option value="D">Disable</option></select>
|
||||||
<div class="ug-form-group">
|
</div>
|
||||||
<label for="editStatus" class="ug-form-label">Status<span class="required-field">*</span></label>
|
<div class="clr-col-sm-12">
|
||||||
<select id="editStatus" class="ug-form-select" [(ngModel)]="rowSelected.status" name="status">
|
<label for="tags">Group Level<span class="required-field">*</span></label>
|
||||||
<option value="E">Enable</option>
|
<select id="" [(ngModel)]="rowSelected.groupLevel" name="groupLevel">
|
||||||
<option value="D">Disable</option>
|
<option >10</option>
|
||||||
</select>
|
<option >20</option>
|
||||||
</div>
|
<option >30</option>
|
||||||
|
<option >40</option>
|
||||||
<div class="ug-form-group">
|
<option>50</option>
|
||||||
<label for="editGroupLevel" class="ug-form-label">Group Level<span class="required-field">*</span></label>
|
</select>
|
||||||
<select id="editGroupLevel" class="ug-form-select" [(ngModel)]="rowSelected.groupLevel" name="groupLevel">
|
</div>
|
||||||
<option value="10">10</option>
|
</div>
|
||||||
<option value="20">20</option>
|
<br>
|
||||||
<option value="30">30</option>
|
<div class="modal-footer">
|
||||||
<option value="40">40</option>
|
<button type="button" class="btn btn-outline" (click)="modaledit = false">Cancel</button>
|
||||||
<option value="50">50</option>
|
<button type="submit" class="btn btn-primary" >Update</button>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
<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>
|
|
||||||
</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.usrGrp">
|
<div class="modal-body" *ngIf="rowSelected.usrGrp">
|
||||||
<div class="ug-delete-header">
|
<h1 class="delete">Are You Sure Want to delete?</h1>
|
||||||
<div class="ug-delete-icon">
|
<h2 class="heading">{{rowSelected.usrGrp}}</h2>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="ug-btn ug-btn-outline" (click)="modaldelete = false">Cancel</button>
|
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
|
||||||
<button type="submit" (click)="delete(rowSelected.usrGrp)" class="ug-btn ug-btn-error">Delete</button>
|
<button type="submit" (click)="delete(rowSelected.usrGrp)" class="btn btn-primary" >Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
|
<clr-modal [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
|
||||||
<h3 class="modal-title">Import File</h3>
|
<h3 class="modal-title">Import File</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="ug-form-group">
|
<input type="file" name="file" class="file" (change)="selectFile($event)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="ug-btn ug-btn-outline" type="button" (click)="modalCsv = false">Cancel</button>
|
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
|
||||||
<button class="ug-btn ug-btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
|
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
@@ -1,748 +1,8 @@
|
|||||||
@import '../../../../../styles.scss';
|
@import '../../../../../styles1.scss';
|
||||||
|
input.ng-invalid.ng-touched {
|
||||||
// Import design tokens
|
border-color: red;
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ug-btn-text {
|
.error_mess {
|
||||||
margin-left: 8px;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ExcelService } from '../../../../services/excel.service';
|
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 { MenuGroupService } from 'src/app/services/admin/menu-group.service';
|
||||||
import { CsvService } from 'src/app/services/csv.service';
|
import { CsvService } from 'src/app/services/csv.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { ThemeService } from 'src/app/services/theme.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-usergrpmaintenance',
|
selector: 'app-usergrpmaintenance',
|
||||||
templateUrl: './usergrpmaintenance.component.html',
|
templateUrl: './usergrpmaintenance.component.html',
|
||||||
styleUrls: ['./usergrpmaintenance.component.scss']
|
styleUrls: ['./usergrpmaintenance.component.scss']
|
||||||
})
|
})
|
||||||
export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
|
export class UsergrpmaintenanceComponent implements OnInit {
|
||||||
loading = false;
|
loading = false;
|
||||||
public entryForm: FormGroup;
|
public entryForm: FormGroup;
|
||||||
givendata;
|
givendata;
|
||||||
@@ -26,13 +23,10 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
|
|||||||
modaledit=false;
|
modaledit=false;
|
||||||
modaldelete=false;
|
modaldelete=false;
|
||||||
rowSelected :any= {};
|
rowSelected :any= {};
|
||||||
mcreate: string | boolean = false;
|
mcreate;
|
||||||
medit: string | boolean = false;
|
medit;
|
||||||
showdata: any;
|
showdata;
|
||||||
submitted=false;
|
submitted=false;
|
||||||
filterText: string = '';
|
|
||||||
viewMode: 'table' | 'cards' = 'cards';
|
|
||||||
private themeSubscription: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private excel: ExcelService,
|
private excel: ExcelService,
|
||||||
@@ -44,32 +38,21 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
|
|||||||
private mainservice:UsergrpmaintainceService,
|
private mainservice:UsergrpmaintainceService,
|
||||||
private csvService: CsvService,
|
private csvService: CsvService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private themeService: ThemeService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
switchLanguage(language: string) {
|
switchLanguage(language: string) {
|
||||||
this.translate.use(language);
|
this.translate.use(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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();
|
this.showdata = this.menuGroupService.getdata();
|
||||||
console.log('Showdata:', this.showdata);
|
console.log(this.showdata);
|
||||||
if (this.showdata) {
|
this.mcreate=this.showdata.mcreate;
|
||||||
// Handle both string and boolean values
|
console.log(this.mcreate);
|
||||||
this.mcreate = this.showdata.mcreate === 'true' || this.showdata.mcreate === true ? true : false;
|
this.medit=this.showdata.medit
|
||||||
this.medit = this.showdata.medit === 'true' || this.showdata.medit === true ? true : false;
|
console.log(this.medit);
|
||||||
console.log('MCREATE:', this.mcreate);
|
|
||||||
console.log('MEDIT:', this.medit);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mainservice.getAll().subscribe((data) => {
|
this.mainservice.getAll().subscribe((data) => {
|
||||||
console.log('Data received:', data);
|
console.log(data);
|
||||||
this.givendata = data || [];
|
this.givendata = data;
|
||||||
if(this.givendata.length==0){
|
if(this.givendata.length==0){
|
||||||
this.error="No data Available";
|
this.error="No data Available";
|
||||||
console.log(this.error)
|
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() {
|
onExport() {
|
||||||
this.excel.exportAsExcelFile(this.givendata, 'user_',
|
this.excel.exportAsExcelFile(this.givendata, 'user_',
|
||||||
moment().format('YYYYMMDD_HHmmss'))
|
moment().format('YYYYMMDD_HHmmss'))
|
||||||
@@ -122,8 +81,7 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
|
|||||||
//this.router.navigate(["../usermaintanceadd"],{relativeTo:this.route});
|
//this.router.navigate(["../usermaintanceadd"],{relativeTo:this.route});
|
||||||
}
|
}
|
||||||
goToEdit(row){
|
goToEdit(row){
|
||||||
console.log('Edit clicked for row:', row);
|
this.rowSelected = row;
|
||||||
this.rowSelected = {...row}; // Create a copy to avoid reference issues
|
|
||||||
this.modaledit=true;
|
this.modaledit=true;
|
||||||
//this.router.navigate(["../usermaintanceedit/"+ id], { relativeTo: this.route });
|
//this.router.navigate(["../usermaintanceedit/"+ id], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
@@ -177,8 +135,7 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
onDelete(row) {
|
onDelete(row) {
|
||||||
console.log('Delete clicked for row:', row);
|
this.rowSelected = row;
|
||||||
this.rowSelected = {...row}; // Create a copy to avoid reference issues
|
|
||||||
this.modaldelete=true;
|
this.modaldelete=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,4 +200,4 @@ export class UsergrpmaintenanceComponent implements OnInit, OnDestroy {
|
|||||||
this.csvService.downloadCsvs(this.ttype);
|
this.csvService.downloadCsvs(this.ttype);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,202 +1,124 @@
|
|||||||
<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://" [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://"><clr-icon shape="lock"></clr-icon>{{ 'SECURITY' | translate }}</a></li>
|
||||||
<li><a href="javascript://">{{'USER_MAINTENANCE'| translate}}</a></li>
|
<li><a href="javascript://">{{'USER_MAINTENANCE'| translate}}</a></li>
|
||||||
|
|
||||||
</ol>
|
</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="dg-wrapper">
|
||||||
<div class="um-toolbar__left">
|
<div class="clr-row">
|
||||||
<div class="um-search">
|
<div class="clr-col">
|
||||||
<clr-icon shape="search"></clr-icon>
|
<h3>{{'USER_MAINTENANCE'| translate}} </h3>
|
||||||
<input class="um-search__input" type="text" [placeholder]="'SEARCH' | translate" [(ngModel)]="filterText" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="um-toolbar__right">
|
<div class="clr-col" style="text-align: right;">
|
||||||
<div class="um-view-toggle">
|
<div class="btn-group">
|
||||||
<button class="um-btn um-btn-sm" [class.um-btn-primary]="viewMode==='cards'" (click)="setViewMode('cards')">
|
<button class="btn text-dark" (click)="downloadFiles()"><b>{{ 'DOWNLOAD_TEMPLATE' | translate }}</b></button>
|
||||||
<clr-icon shape="view-cards"></clr-icon>
|
|
||||||
|
</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>
|
||||||
<button class="um-btn um-btn-sm" [class.um-btn-primary]="viewMode==='table'" (click)="setViewMode('table')">
|
<button id="add" class="btn btn-primary" *ngIf="mcreate == 'true'" (click)="goToAdd()">
|
||||||
<clr-icon shape="table"></clr-icon>
|
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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
|
||||||
|
</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 givendata;let i = index" [clrDgItem]="user">
|
||||||
|
<clr-dg-cell>{{user.userId}}</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="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>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<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 *ngIf="error;else loadingSpinner">{{error}}</div>
|
|
||||||
</clr-dg-placeholder>
|
|
||||||
|
|
||||||
<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-row *clrDgItems="let user of filteredUsers;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.email}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{user.usrGrpName}}</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>
|
|
||||||
</clr-dg-action-overflow>
|
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<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-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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||||
|
|
||||||
<div class="modal-body" *ngIf="rowSelected.userId">
|
<div class="modal-body" *ngIf="rowSelected.userId">
|
||||||
<div class="um-delete-header">
|
<h1 class="delete">Are You Sure Want to delete?</h1>
|
||||||
<div class="um-delete-icon">
|
<h2 class="heading">{{rowSelected.userId}}</h2>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="um-btn um-btn-outline" (click)="modaldelete = false">{{'CANCEL' |
|
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
|
||||||
translate}}</button>
|
<button type="submit" (click)="delete(rowSelected.userId)" class="btn btn-primary" >Delete</button>
|
||||||
<button type="submit" (click)="delete(rowSelected.userId)" class="um-btn um-btn-error">{{'DELETE' |
|
|
||||||
translate}}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="modalCsv" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="false">
|
<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="modal-body">
|
||||||
<div class="um-form-group">
|
<input type="file" name="file" class="file" (change)="selectFile($event)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="um-btn um-btn-outline" type="button" (click)="modalCsv = false">{{'CANCEL' | translate}}</button>
|
<button class="btn btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">Import</button>
|
||||||
<button class="um-btn um-btn-primary" type="button" [disabled]="!selectedFiles" (click)="saveCsv()">{{'IMPORT' |
|
|
||||||
translate}}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|||||||
@@ -1,656 +1,4 @@
|
|||||||
@import '../../../../../styles.scss';
|
.delete,.heading{
|
||||||
|
|
||||||
// Import design tokens
|
|
||||||
@import '../../../../../styles/design-tokens';
|
|
||||||
|
|
||||||
.delete, .heading {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: red;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ExcelService } from '../../../../services/excel.service';
|
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 { ToastrService } from 'ngx-toastr';
|
||||||
import { CsvService } from 'src/app/services/csv.service';
|
import { CsvService } from 'src/app/services/csv.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { ThemeService } from 'src/app/services/theme.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-usermaintance',
|
selector: 'app-usermaintance',
|
||||||
templateUrl: './usermaintance.component.html',
|
templateUrl: './usermaintance.component.html',
|
||||||
styleUrls: ['./usermaintance.component.scss']
|
styleUrls: ['./usermaintance.component.scss']
|
||||||
})
|
})
|
||||||
export class UsermaintanceComponent implements OnInit, OnDestroy {
|
export class UsermaintanceComponent implements OnInit {
|
||||||
loading = false;
|
loading = false;
|
||||||
loading1=false;
|
loading1=false;
|
||||||
public entryForm: FormGroup;
|
public entryForm: FormGroup;
|
||||||
@@ -24,18 +21,13 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
|
|||||||
orders;
|
orders;
|
||||||
modalAdd= false;
|
modalAdd= false;
|
||||||
modaledit=false;
|
modaledit=false;
|
||||||
mcreate: string | boolean = false;
|
mcreate;
|
||||||
medit: string | boolean = false;
|
medit;
|
||||||
mdelete;
|
mdelete;
|
||||||
showdata;
|
showdata;
|
||||||
error;
|
error;
|
||||||
modaldelete=false;
|
modaldelete=false;
|
||||||
rowSelected :any= {};
|
rowSelected :any= {};
|
||||||
// UI-only state
|
|
||||||
filterText: string = '';
|
|
||||||
viewMode: 'table' | 'cards' = 'cards';
|
|
||||||
private themeSubscription: Subscription;
|
|
||||||
|
|
||||||
constructor(private excel: ExcelService,
|
constructor(private excel: ExcelService,
|
||||||
private _fb: FormBuilder,
|
private _fb: FormBuilder,
|
||||||
private router: Router, private toastr:ToastrService,
|
private router: Router, private toastr:ToastrService,
|
||||||
@@ -44,41 +36,25 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
|
|||||||
private mainservice:UsermaintanceService,
|
private mainservice:UsermaintanceService,
|
||||||
private csvService: CsvService,
|
private csvService: CsvService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private themeService: ThemeService,
|
|
||||||
) {this.loading1 = true;
|
) {this.loading1 = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loading1 = false;
|
this.loading1 = false;
|
||||||
}, 1000); }
|
}, 1000); }
|
||||||
|
switchLanguage(language: string) {
|
||||||
switchLanguage(language: string) {
|
this.translate.use(language);
|
||||||
this.translate.use(language);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
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();
|
this.showdata = this.menuGroupService.getdata();
|
||||||
console.log(this.showdata);
|
console.log(this.showdata);
|
||||||
if (this.showdata) {
|
this.mcreate=this.showdata.mcreate;
|
||||||
this.mcreate = this.showdata.mcreate === 'true' || this.showdata.mcreate === true ? true : false;
|
|
||||||
console.log(this.mcreate);
|
console.log(this.mcreate);
|
||||||
this.mdelete=this.showdata.mdelete
|
this.mdelete=this.showdata.mdelete
|
||||||
console.log(this.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);
|
console.log(this.medit);
|
||||||
}
|
|
||||||
this.getData();
|
this.getData();
|
||||||
}
|
|
||||||
|
}
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.themeSubscription) {
|
|
||||||
this.themeSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getData(){
|
getData(){
|
||||||
this.mainservice.getAll().subscribe((data) => {
|
this.mainservice.getAll().subscribe((data) => {
|
||||||
console.log(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
|
// csv
|
||||||
|
|
||||||
@@ -170,7 +129,7 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigate(["../usermaintancedit/"+ id], { relativeTo: this.route });
|
this.router.navigate(["../usermaintancedit/"+ id], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
onDelete(row) {
|
onDelete(row) {
|
||||||
this.rowSelected = {...row}; // Create a copy to avoid reference issues
|
this.rowSelected = row;
|
||||||
this.modaldelete=true;
|
this.modaldelete=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,4 +155,4 @@ export class UsermaintanceComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,282 +1,319 @@
|
|||||||
<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">
|
<h4 style="font-weight: 300;display: inline;"> User Maintenance</h4>
|
||||||
<div class="um-hero__content">
|
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Add Mode</span><br>
|
||||||
<div class="um-hero__icon"><clr-icon shape="user"></clr-icon></div>
|
<hr>
|
||||||
<div>
|
<div class="main" >
|
||||||
<h2 class="um-hero__title">User Maintenance</h2>
|
<form [formGroup]="entryForm">
|
||||||
<p class="um-hero__subtitle">Add New User</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="um-add-container">
|
<div class="clr-row">
|
||||||
<div class="um-form-card">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()" class="um-form">
|
<label for="name"> First Name</label>
|
||||||
<div class="clr-row">
|
<input type="text" class="clr-input" formControlName="first_name">
|
||||||
<div class="clr-col-md-6 clr-col-sm-12">
|
<div *ngIf="submitted && entryForm.controls.first_name.errors" class="error_mess">
|
||||||
<label for="first_name" class="um-form-label">First Name<span class="required-field">*</span></label>
|
<div *ngIf="submitted && entryForm.controls.first_name.errors.required" class="error_mess">*This field is Required</div>
|
||||||
<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>
|
|
||||||
</div>
|
</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">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-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>
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
|
<label for="name">Mobile Number</label>
|
||||||
<div class="clr-col-md-6 clr-col-sm-12">
|
<input type="text" class="clr-input" formControlName="mob_no" required pattern="((\+)?([1-9]{2}))?[1-9]{1}[0-9]{9}$">
|
||||||
<label for="email" class="um-form-label">Email<span class="required-field">*</span></label>
|
<div *ngIf="submitted && entryForm.controls.mob_no.errors" class="error_mess">
|
||||||
<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.mob_no.errors.required" class="error_mess">*This field is Required</div>
|
||||||
<div *ngIf="submitted && entryForm.controls.email.errors" class="um-error-message">
|
<div *ngIf="submitted && entryForm.controls.mob_no.errors.pattern" class="error_mess">* Please Follow your pattern,+91 Enter 10 digit Mobile Number.
|
||||||
<div *ngIf="submitted && entryForm.controls.email.errors.required">*This field is Required</div>
|
</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>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Customer Modal -->
|
</div>
|
||||||
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
|
</div>
|
||||||
<h3 class="modal-title"><b>Select From List:</b></h3>
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<div class="modal-body">
|
<label for="name">New Password<span class="required-field">*</span></label>
|
||||||
<div class="clr-row">
|
<input type="password" class="clr-input" formControlName="new_password" minlenght="6">
|
||||||
<div class="clr-col-md-12 clr-col-sm-12">
|
<div *ngIf="submitted && entryForm.controls.new_password.errors" class="error_mess">
|
||||||
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
|
<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>
|
||||||
|
</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>
|
||||||
|
</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-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-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="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>
|
||||||
|
</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="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>
|
||||||
|
<!-- <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 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>
|
||||||
</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}">
|
|
||||||
No
|
|
||||||
</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}">
|
|
||||||
Customer Id
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'customerName'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Customer Name
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of custdata;let i = index" [clrDgItem]="user">
|
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
|
||||||
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
<h3 class="modal-title"><b>Select From List:</b></h3>
|
||||||
<clr-dg-cell (click)="getcustID(user.customerId)" class="um-clickable-cell">{{user.customerCode}}</clr-dg-cell>
|
<div class="modal-body">
|
||||||
<clr-dg-cell>{{user.customerId}}</clr-dg-cell>
|
<div class="clr-row">
|
||||||
<clr-dg-cell>{{user.customerName}}</clr-dg-cell>
|
<div class="clr-col-md-12 clr-col-sm-12">
|
||||||
|
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<table class="table">
|
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
<tr>
|
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<td class="td-title">No</td>
|
No
|
||||||
<td class="td-content">{{user.id}}</td>
|
</ng-container></clr-dg-column>
|
||||||
</tr>
|
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<tr>
|
Customer Code
|
||||||
<td class="td-title">Customer Code</td>
|
</ng-container></clr-dg-column>
|
||||||
<td class="td-content">{{user.customerCode}}</td>
|
<clr-dg-column [clrDgField]="'customerCode'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</tr>
|
Customer Id
|
||||||
<tr>
|
</ng-container></clr-dg-column>
|
||||||
<td class="td-title">Customer Id</td>
|
|
||||||
<td class="td-content">{{user.customerId}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="td-title">Customer Name</td>
|
|
||||||
<td class="td-content">{{user.customerName}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</clr-dg-row-detail>
|
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer>
|
<clr-dg-column [clrDgField]="'customerName'">
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records per page</clr-dg-page-size>
|
Customer Name
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
</ng-container>
|
||||||
of {{pagination.totalItems}} Records
|
</clr-dg-column >
|
||||||
</clr-dg-pagination>
|
|
||||||
</clr-dg-footer>
|
|
||||||
</clr-datagrid>
|
|
||||||
</div>
|
|
||||||
</clr-modal>
|
|
||||||
|
|
||||||
<!-- Department Modal -->
|
<clr-dg-row *clrDgItems="let user of custdata;let i = index" [clrDgItem]="user">
|
||||||
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
|
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
||||||
<h3 class="modal-title"><b>Select From List:</b></h3>
|
<clr-dg-cell (click)="getcustID(user.customerId)" style="color: rgb(108, 108, 194);" >{{user.customerCode}}</clr-dg-cell>
|
||||||
<div class="modal-body">
|
<clr-dg-cell >{{user.customerId}}</clr-dg-cell>
|
||||||
<div class="clr-row">
|
<clr-dg-cell>{{user.customerName}}</clr-dg-cell>
|
||||||
<div class="clr-col-md-12 clr-col-sm-12">
|
|
||||||
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<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-content">{{user.customerCode}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td-title">Customer Id</td>
|
||||||
|
<td class="td-content">{{user.customerId}} </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td-title"> Customer Name<td>
|
||||||
|
<td class="td-content">{{user.customerName}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records 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>
|
</div>
|
||||||
</div>
|
</clr-modal>
|
||||||
|
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
|
||||||
<clr-dg-placeholder>
|
<h3 class="modal-title"><b>Select From List:</b></h3>
|
||||||
<clr-spinner>Loading ...</clr-spinner>
|
<div class="modal-body">
|
||||||
</clr-dg-placeholder>
|
<div class="clr-row">
|
||||||
<clr-dg-column [clrDgField]="'id'">
|
<div class="clr-col-md-12 clr-col-sm-12">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
|
||||||
No
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'customerId'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
Department Code
|
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
</ng-container>
|
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-column>
|
No
|
||||||
|
</ng-container></clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
Department Code
|
||||||
|
</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)" 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-content">{{user.id}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records per page</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
|
of {{pagination.totalItems}} Records
|
||||||
|
</clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
|
||||||
<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-row-detail *clrIfExpanded>
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="td-title">No</td>
|
|
||||||
<td class="td-content">{{user.id}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</clr-dg-row-detail>
|
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer>
|
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records 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>
|
|
||||||
</clr-modal>
|
|
||||||
|
|
||||||
<!-- Position 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">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</clr-modal>
|
||||||
|
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
<clr-modal [(clrModalOpen)]="position" [clrModalSize]="'lg'">
|
||||||
<clr-dg-placeholder>
|
<h3 class="modal-title"><b>Select From List:</b></h3>
|
||||||
<clr-spinner>Loading ...</clr-spinner>
|
<div class="modal-body">
|
||||||
</clr-dg-placeholder>
|
<div class="clr-row">
|
||||||
<clr-dg-column [clrDgField]="'id'">
|
<div class="clr-col-md-12 clr-col-sm-12">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
|
||||||
No
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'customerId'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Position Code
|
|
||||||
</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-row-detail *clrIfExpanded>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<table class="table">
|
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
<tr>
|
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<td class="td-title">No</td>
|
No
|
||||||
<td class="td-content">{{user.id}}</td>
|
</ng-container></clr-dg-column>
|
||||||
</tr>
|
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</table>
|
Position Code
|
||||||
</clr-dg-row-detail>
|
</ng-container></clr-dg-column>
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer>
|
<clr-dg-row *clrDgItems="let user of positiondata;let i = index" [clrDgItem]="user">
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records per page</clr-dg-page-size>
|
<clr-dg-cell (click)="getposid(user.positionCode)" style="color: rgb(108, 108, 194);" >{{user.positionCode}}</clr-dg-cell>
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
|
||||||
of {{pagination.totalItems}} Records
|
|
||||||
</clr-dg-pagination>
|
|
||||||
</clr-dg-footer>
|
|
||||||
</clr-datagrid>
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
</div>
|
<table class="table">
|
||||||
</clr-modal>
|
<tr>
|
||||||
|
<td class="td-title">NO</td>
|
||||||
|
<td class="td-content">{{user.id}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records 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>
|
||||||
|
</clr-modal>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
@import '../../../../../styles1.scss';
|
@import '../../../../../styles1.scss';
|
||||||
@import '../../../../../styles/_design-tokens.scss';
|
|
||||||
|
|
||||||
input.ng-invalid.ng-touched {
|
input.ng-invalid.ng-touched {
|
||||||
border-color: red;
|
border-color: red;
|
||||||
}
|
}
|
||||||
@@ -8,383 +6,3 @@ input.ng-invalid.ng-touched {
|
|||||||
.error_mess {
|
.error_mess {
|
||||||
color: red;
|
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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,6 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { AccesstypeService } from 'src/app/services/admin/accesstype.service';
|
import { AccesstypeService } from 'src/app/services/admin/accesstype.service';
|
||||||
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
||||||
import { UsermaintanceService } from '../../../../services/admin/usermaintance.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({
|
@Component({
|
||||||
selector: 'app-usermaintanceadd',
|
selector: 'app-usermaintanceadd',
|
||||||
@@ -15,63 +12,51 @@ import { Subscription } from 'rxjs';
|
|||||||
})
|
})
|
||||||
export class UsermaintanceaddComponent implements OnInit {
|
export class UsermaintanceaddComponent implements OnInit {
|
||||||
public entryForm: FormGroup;
|
public entryForm: FormGroup;
|
||||||
customer: boolean = false;
|
customer:boolean=false;
|
||||||
department: boolean = false;
|
department:boolean=false;
|
||||||
position: boolean = false;
|
position:boolean=false;
|
||||||
custdata: any;
|
custdata: any;
|
||||||
loading = false;
|
loading = false;
|
||||||
clickedID: number;
|
clickedID:number;
|
||||||
custiddata: any;
|
custiddata: any;
|
||||||
userobjcust = {
|
userobjcust={
|
||||||
customerName: '',
|
customerName:'',
|
||||||
customerCode: '',
|
customerCode:'',
|
||||||
}
|
}
|
||||||
departmentdata: any;
|
departmentdata: any;
|
||||||
positiondata: any;
|
positiondata: any;
|
||||||
deptiddata: any;
|
deptiddata: any;
|
||||||
userobjdept = {
|
userobjdept={
|
||||||
departmentCode: '',
|
departmentCode:'',
|
||||||
}
|
}
|
||||||
userobjpos = {
|
userobjpos={
|
||||||
positionCode: '',
|
positionCode:'',
|
||||||
}
|
}
|
||||||
posiddata: any;
|
posiddata: any;
|
||||||
usergrpdata;
|
usergrpdata;
|
||||||
accessdata;
|
accessdata;
|
||||||
error;
|
error;
|
||||||
submitted = false;
|
submitted=false;
|
||||||
private themeSubscription: Subscription;
|
|
||||||
|
|
||||||
constructor(private _fb: FormBuilder,
|
constructor( private _fb: FormBuilder,
|
||||||
private mainservice: UsermaintanceService,
|
private mainservice:UsermaintanceService,
|
||||||
private router: Router, private accesstype: AccesstypeService,
|
private router: Router,private accesstype:AccesstypeService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private usergrpservice: UsergrpmaintainceService,
|
private usergrpservice: UsergrpmaintainceService
|
||||||
private translate: TranslateService,
|
) { }
|
||||||
private themeService: ThemeService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
switchLanguage(language: string) {
|
|
||||||
this.translate.use(language);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
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({
|
this.entryForm = this._fb.group({
|
||||||
first_name: ['', [Validators.required]],
|
first_name :['',[Validators.required]],
|
||||||
last_name: ['', [Validators.required]],
|
last_name:['',[Validators.required]],
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email:['',[Validators.required,Validators.email]],
|
||||||
mob_no: ['', [Validators.required]],
|
mob_no:['',[Validators.required]],
|
||||||
new_password: ['', [Validators.required, Validators.minLength(6)]],
|
new_password:['',[Validators.required,Validators.minLength(6)]],
|
||||||
confirm_password: ['', [Validators.required]],
|
confirm_password:['',[Validators.required]],
|
||||||
usrGrpId: ['', [Validators.required]],
|
usrGrpId:['',[Validators.required]],
|
||||||
account_id: 1,
|
account_id:1,
|
||||||
accesstype: ['', [Validators.required]],
|
accesstype:['',[Validators.required]],
|
||||||
// status:[null],
|
// status:[null],
|
||||||
// username:[null] ,
|
// username:[null] ,
|
||||||
// userPassw:[null] ,
|
// userPassw:[null] ,
|
||||||
// confirmPassword:[null],
|
// confirmPassword:[null],
|
||||||
@@ -87,38 +72,31 @@ export class UsermaintanceaddComponent implements OnInit {
|
|||||||
// notification:[null],
|
// notification:[null],
|
||||||
|
|
||||||
//departmentCode: this._fb.array([this.department()]),
|
//departmentCode: this._fb.array([this.department()]),
|
||||||
// positionCode: this._fb.array([this.position()]),
|
// positionCode: this._fb.array([this.position()]),
|
||||||
//usrGrp: this._fb.array([this.user()]),
|
//usrGrp: this._fb.array([this.user()]),
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
validator: ConfirmedValidator('new_password', 'confirm_password')
|
validator: ConfirmedValidator('new_password', 'confirm_password')
|
||||||
});
|
});
|
||||||
this.usergrp();
|
this.usergrp();
|
||||||
this.getdata();
|
this.getdata();
|
||||||
}
|
}
|
||||||
|
getdata(){
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.themeSubscription) {
|
|
||||||
this.themeSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getdata() {
|
|
||||||
this.accesstype.getAll().subscribe(resp => {
|
this.accesstype.getAll().subscribe(resp => {
|
||||||
this.accessdata = resp;
|
this.accessdata = resp;
|
||||||
console.log('accessdata: ', this.accessdata);
|
console.log('accessdata: ', this.accessdata);
|
||||||
if (this.accessdata.length == 0) {
|
if(this.accessdata.length==0){
|
||||||
this.error = "No data Available";
|
this.error="No data Available";
|
||||||
console.log(this.error)
|
console.log(this.error)
|
||||||
}
|
}
|
||||||
}, (error) => {
|
},(error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
if (error) {
|
if(error){
|
||||||
this.error = "Server Error";
|
this.error="Server Error";
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
usergrp() {
|
usergrp(){
|
||||||
this.usergrpservice.getAll().subscribe((data) => {
|
this.usergrpservice.getAll().subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.usergrpdata = data;
|
this.usergrpdata = data;
|
||||||
@@ -142,11 +120,11 @@ export class UsermaintanceaddComponent implements OnInit {
|
|||||||
|
|
||||||
// });
|
// });
|
||||||
//}
|
//}
|
||||||
onSubmit() {
|
onSubmit(){
|
||||||
this.submitted = true
|
this.submitted=true
|
||||||
if (this.entryForm.invalid) {
|
if (this.entryForm.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(this.entryForm.value);
|
console.log(this.entryForm.value);
|
||||||
this.mainservice.create(this.entryForm.value).subscribe(data => {
|
this.mainservice.create(this.entryForm.value).subscribe(data => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
@@ -158,63 +136,63 @@ export class UsermaintanceaddComponent implements OnInit {
|
|||||||
this.router.navigate(["../usermaintance"], { relativeTo: this.route });
|
this.router.navigate(["../usermaintance"], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
goback() {
|
goback(){
|
||||||
this.router.navigate(["../usermaintance"], { relativeTo: this.route });
|
this.router.navigate(["../usermaintance"], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
gotodepartmet() {
|
gotodepartmet(){
|
||||||
this.department = !this.department;
|
this.department=!this.department;
|
||||||
this.mainservice.getalldepartment().subscribe((data) => {
|
this.mainservice.getalldepartment().subscribe((data)=>{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.departmentdata = data;
|
this.departmentdata=data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getdepid(id: number) {
|
getdepid(id:number){
|
||||||
this.clickedID = id;
|
this.clickedID=id;
|
||||||
console.log("clicked by id" + id);
|
console.log("clicked by id"+ id);
|
||||||
this.mainservice.getbydepartmentid(id).subscribe((data) => {
|
this.mainservice.getbydepartmentid(id).subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.deptiddata = data;
|
this.deptiddata= data;
|
||||||
// this.userObj= this.custiddata;
|
// this.userObj= this.custiddata;
|
||||||
this.userobjdept = this.deptiddata;
|
this.userobjdept =this.deptiddata;
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
this.department = false;
|
this.department=false;
|
||||||
}
|
}
|
||||||
gotoposition() {
|
gotoposition(){
|
||||||
this.position = !this.position;
|
this.position=!this.position;
|
||||||
this.mainservice.getallposition().subscribe((data) => {
|
this.mainservice.getallposition().subscribe((data)=>{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.positiondata = data;
|
this.positiondata=data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
getposid(id: number) {
|
getposid(id:number){
|
||||||
this.clickedID = id;
|
this.clickedID=id;
|
||||||
console.log("clicked by id" + id);
|
console.log("clicked by id"+ id);
|
||||||
this.mainservice.getbypositionid(id).subscribe((data) => {
|
this.mainservice.getbypositionid(id).subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.posiddata = data;
|
this.posiddata= data;
|
||||||
// this.userObj= this.custiddata;
|
// 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) => {
|
return (formGroup: FormGroup) => {
|
||||||
const control = formGroup.controls[controlName];
|
const control = formGroup.controls[controlName];
|
||||||
const matchingControl = formGroup.controls[matchingControlName];
|
const matchingControl = formGroup.controls[matchingControlName];
|
||||||
if (matchingControl.errors && !matchingControl.errors.confirmedValidator) {
|
if (matchingControl.errors && !matchingControl.errors.confirmedValidator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control.value !== matchingControl.value) {
|
if (control.value !== matchingControl.value) {
|
||||||
matchingControl.setErrors({ confirmedValidator: true });
|
matchingControl.setErrors({ confirmedValidator: true });
|
||||||
} else {
|
} else {
|
||||||
matchingControl.setErrors(null);
|
matchingControl.setErrors(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,249 +1,348 @@
|
|||||||
<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">
|
<h4 style="font-weight: 300;display: inline;"> User Maintenance</h4>
|
||||||
<div class="um-hero__content">
|
<span class="label label-light-blue" style="display: inline;margin-left: 30px;">Edit Mode</span><br>
|
||||||
<div class="um-hero__icon"><clr-icon shape="user"></clr-icon></div>
|
<hr>
|
||||||
<div>
|
<div class="main" >
|
||||||
<h2 class="um-hero__title">User Maintenance</h2>
|
<form *ngIf="data1">
|
||||||
<p class="um-hero__subtitle">Edit User</p>
|
<div class="clr-row">
|
||||||
</div>
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
</div>
|
<label for="name"> First Name</label>
|
||||||
</section>
|
<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="um-edit-container">
|
<!-- <div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<div class="um-form-card">
|
<label for="name">Business Title</label>
|
||||||
<form *ngIf="data1" (ngSubmit)="update()" class="um-form">
|
<input type="text" class="clr-input" formControlName="title">
|
||||||
<div class="clr-row">
|
</div>
|
||||||
<div class="clr-col-md-6 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label for="first_name" class="um-form-label">First Name<span class="required-field">*</span></label>
|
<label for="name"> Short Name</label>
|
||||||
<input type="text" id="first_name" class="um-form-input" name="first_name" [(ngModel)]="data1.fullName">
|
<input type="text" class="clr-input" formControlName="shortName">
|
||||||
</div>
|
</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-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-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 class="clr-col-md-6 clr-col-sm-12">
|
</div>
|
||||||
<label for="last_name" class="um-form-label">Last Name<span class="required-field">*</span></label>
|
<!-- <div class="clr-row">
|
||||||
<input type="text" id="last_name" class="um-form-input" name="last_name" [(ngModel)]="data1.last_name">
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<div class="clr-col-md-6 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label for="email" class="um-form-label">Email<span class="required-field">*</span></label>
|
<label for="name"> User Name</label>
|
||||||
<input type="text" id="email" class="um-form-input" name="email" [(ngModel)]="data1.email">
|
<input type="text" class="clr-input" [(ngModel)]="data1.username" name="username">
|
||||||
</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" [(ngModel)]="data1.userPassw" name="userPassw">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="clr-col-md-6 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label for="new_password" class="um-form-label">New Password<span class="required-field">*</span></label>
|
<label for="name">Business Title</label>
|
||||||
<input type="password" id="new_password" class="um-form-input" name="new_password" [(ngModel)]="data1.userPassw">
|
<input type="text" class="clr-input" [(ngModel)]="data1.title" name="title">
|
||||||
</div>
|
</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 class="clr-col-md-6 clr-col-sm-12">
|
</div>
|
||||||
<label for="usrGrpId" class="um-form-label">User Group<span class="required-field">*</span></label>
|
<div class="clr-row">
|
||||||
<select id="usrGrpId" class="um-form-select" [(ngModel)]="data1.usrGrpId" name="usrGrpId">
|
<div class="clr-col-md-3 clr-col-sm-12">
|
||||||
<option value="">Select User Group</option>
|
<label for="name">Created By</label>
|
||||||
<option *ngFor="let group of usergrpdata" [value]="group.usrGrp">{{group.groupName}}</option>
|
<input type="text" class="clr-input" [(ngModel)]="data1.createby" name="createby">
|
||||||
</select>
|
</div>
|
||||||
</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 class="clr-col-md-6 clr-col-sm-12">
|
<div class="modal-footer">
|
||||||
<label for="accesstype" class="um-form-label">Access Type<span class="required-field">*</span></label>
|
<button type="button" class="btn btn-outline" (click)="goback()">Close</button>
|
||||||
<select id="accesstype" class="um-form-select" [(ngModel)]="data1.accesstype" name="accesstype">
|
<button type="button" class="btn btn-primary" (click)="update()">Update</button>
|
||||||
<option value="">Select Access Type</option>
|
|
||||||
<option *ngFor="let access of accessdata" [value]="access.name">{{access.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Customer Modal -->
|
|
||||||
<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">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
<clr-modal [(clrModalOpen)]="customer" [clrModalSize]="'lg'">
|
||||||
<clr-dg-placeholder>
|
<h3 class="modal-title"><b>Select From List:</b></h3>
|
||||||
<clr-spinner>Loading ...</clr-spinner>
|
<div class="modal-body">
|
||||||
</clr-dg-placeholder>
|
<div class="clr-row">
|
||||||
<clr-dg-column [clrDgField]="'id'">
|
<div class="clr-col-md-12 clr-col-sm-12">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
|
||||||
No
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
</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}">
|
|
||||||
Customer Id
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'customerName'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Customer Name
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of custdata;let i = index" [clrDgItem]="user">
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
<clr-dg-cell (click)="getcustID(user.customerId)" class="um-clickable-cell">{{user.customerCode}}</clr-dg-cell>
|
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-dg-cell>{{user.customerId}}</clr-dg-cell>
|
No
|
||||||
<clr-dg-cell>{{user.customerName}}</clr-dg-cell>
|
</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}">
|
||||||
|
Customer Id
|
||||||
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-column [clrDgField]="'customerName'">
|
||||||
<table class="table">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<tr>
|
Customer Name
|
||||||
<td class="td-title">No</td>
|
</ng-container>
|
||||||
<td class="td-content">{{user.id}}</td>
|
</clr-dg-column >
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="td-title">Customer Name</td>
|
|
||||||
<td class="td-content">{{user.customerName}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</clr-dg-row-detail>
|
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer>
|
<clr-dg-row *clrDgItems="let user of custdata;let i = index" [clrDgItem]="user">
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records per page</clr-dg-page-size>
|
<clr-dg-cell (click)="getcustID(user.customerId)" style="color: rgb(108, 108, 194);" >{{user.customerCode}}</clr-dg-cell>
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
<clr-dg-cell >{{user.customerId}}</clr-dg-cell>
|
||||||
of {{pagination.totalItems}} Records
|
<clr-dg-cell>{{user.customerName}}</clr-dg-cell>
|
||||||
</clr-dg-pagination>
|
|
||||||
</clr-dg-footer>
|
|
||||||
</clr-datagrid>
|
|
||||||
</div>
|
|
||||||
</clr-modal>
|
|
||||||
|
|
||||||
<!-- Department Modal -->
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
|
<table class="table">
|
||||||
<h3 class="modal-title"><b>Select From List:</b></h3>
|
<tr>
|
||||||
<div class="modal-body">
|
<td class="td-title">NO</td>
|
||||||
<div class="clr-row">
|
<td class="td-content">{{user.id}}</td>
|
||||||
<div class="clr-col-md-12 clr-col-sm-12">
|
</tr>
|
||||||
<input id="data" type="text" placeholder="Enter search Criteria" class="um-form-input">
|
<tr>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td-title"> Customer Name<td>
|
||||||
|
<td class="td-content">{{user.customerName}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records 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>
|
</div>
|
||||||
</div>
|
</clr-modal>
|
||||||
|
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
<clr-modal [(clrModalOpen)]="department" [clrModalSize]="'lg'">
|
||||||
<clr-dg-placeholder>
|
<h3 class="modal-title"><b>Select From List:</b></h3>
|
||||||
<clr-spinner>Loading ...</clr-spinner>
|
<div class="modal-body">
|
||||||
</clr-dg-placeholder>
|
<div class="clr-row">
|
||||||
<clr-dg-column [clrDgField]="'id'">
|
<div class="clr-col-md-12 clr-col-sm-12">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<input id="data" type="text" placeholder="Enter search Criteria" class="clr-input">
|
||||||
No
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'customerId'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
Department Code
|
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
</ng-container>
|
<clr-dg-column [clrDgField]="'id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-column>
|
No
|
||||||
|
</ng-container></clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'customerId'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
Department Code
|
||||||
|
</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)" 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-content">{{user.id}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records per page</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
|
of {{pagination.totalItems}} Records
|
||||||
|
</clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
|
||||||
<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-row-detail *clrIfExpanded>
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="td-title">No</td>
|
|
||||||
<td class="td-content">{{user.id}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</clr-dg-row-detail>
|
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer>
|
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records 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>
|
|
||||||
</clr-modal>
|
|
||||||
|
|
||||||
<!-- Position 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">
|
|
||||||
</div>
|
</div>
|
||||||
</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="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}">
|
|
||||||
No
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'customerId'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Position Code
|
|
||||||
</ng-container>
|
|
||||||
</clr-dg-column>
|
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of positiondata;let i = index" [clrDgItem]="user">
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
<clr-dg-placeholder><clr-spinner>Loading ...</clr-spinner></clr-dg-placeholder>
|
||||||
<clr-dg-cell (click)="getposid(user.positionCode)" class="um-clickable-cell">{{user.positionCode}}</clr-dg-cell>
|
<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}">
|
||||||
|
Position Code
|
||||||
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-row *clrDgItems="let user of positiondata;let i = index" [clrDgItem]="user">
|
||||||
<table class="table">
|
<clr-dg-cell>{{i+1}}</clr-dg-cell>
|
||||||
<tr>
|
<clr-dg-cell (click)="getposid(user.positionCode)" style="color: rgb(108, 108, 194);" >{{user.positionCode}}</clr-dg-cell>
|
||||||
<td class="td-title">No</td>
|
|
||||||
<td class="td-content">{{user.id}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</clr-dg-row-detail>
|
|
||||||
</clr-dg-row>
|
|
||||||
|
|
||||||
<clr-dg-footer>
|
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records per page</clr-dg-page-size>
|
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
of {{pagination.totalItems}} Records
|
<table class="table">
|
||||||
</clr-dg-pagination>
|
<tr>
|
||||||
</clr-dg-footer>
|
<td class="td-title">NO</td>
|
||||||
</clr-datagrid>
|
<td class="td-content">{{user.id}}</td>
|
||||||
</div>
|
</tr>
|
||||||
</clr-modal>
|
</table>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Records 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>
|
||||||
|
</clr-modal>
|
||||||
|
|||||||
@@ -1,375 +1 @@
|
|||||||
@import '../../../../../styles1.scss';
|
@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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { AccesstypeService } from 'src/app/services/admin/accesstype.service';
|
import { AccesstypeService } from 'src/app/services/admin/accesstype.service';
|
||||||
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
||||||
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
|
import { UsermaintanceService } from '../../../../services/admin/usermaintance.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
//import { Usermain } from 'src/app/models/usermaintaince';
|
||||||
import { ThemeService } from 'src/app/services/theme.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-usermaintanceedit',
|
selector: 'app-usermaintanceedit',
|
||||||
@@ -13,52 +11,40 @@ import { Subscription } from 'rxjs';
|
|||||||
styleUrls: ['./usermaintanceedit.component.scss']
|
styleUrls: ['./usermaintanceedit.component.scss']
|
||||||
})
|
})
|
||||||
export class UsermaintanceeditComponent implements OnInit {
|
export class UsermaintanceeditComponent implements OnInit {
|
||||||
id: number;
|
id:number;
|
||||||
data1: any = {};
|
data1:any={};
|
||||||
customer: boolean = false;
|
customer:boolean=false;
|
||||||
custdata: any;
|
custdata: any;
|
||||||
clickedID: number;
|
clickedID:number;
|
||||||
custiddata: any;
|
custiddata: any;
|
||||||
loading = false;
|
loading = false;
|
||||||
userobjcust = {
|
userobjcust={
|
||||||
customerName: '',
|
customerName:'',
|
||||||
customerCode: '',
|
customerCode:'',
|
||||||
}
|
}
|
||||||
accessdata;
|
accessdata;
|
||||||
department: boolean = false;
|
department:boolean=false;
|
||||||
departmentdata: any;
|
departmentdata: any;
|
||||||
positiondata: any;
|
positiondata: any;
|
||||||
deptiddata: any;
|
deptiddata: any;
|
||||||
userobjdept = {
|
userobjdept={
|
||||||
departmentCode: '',
|
departmentCode:'',
|
||||||
}
|
}
|
||||||
userobjpos = {
|
userobjpos={
|
||||||
positionCode: '',
|
positionCode:'',
|
||||||
}
|
}
|
||||||
posiddata: any;
|
posiddata: any;
|
||||||
position: boolean = false;
|
position:boolean=false;
|
||||||
usergrpdata;
|
usergrpdata;
|
||||||
error;
|
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 {
|
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"];
|
this.id = this.route.snapshot.params["id"];
|
||||||
console.log("update with id = ", this.id);
|
console.log("update with id = ", this.id);
|
||||||
this.getById(this.id);
|
this.getById(this.id);
|
||||||
@@ -66,89 +52,82 @@ export class UsermaintanceeditComponent implements OnInit {
|
|||||||
this.usergrp();
|
this.usergrp();
|
||||||
this.getdata();
|
this.getdata();
|
||||||
}
|
}
|
||||||
|
getdata(){
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.themeSubscription) {
|
|
||||||
this.themeSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getdata() {
|
|
||||||
this.accesstype.getAll().subscribe(resp => {
|
this.accesstype.getAll().subscribe(resp => {
|
||||||
this.accessdata = resp;
|
this.accessdata = resp;
|
||||||
console.log('accessdata: ', this.accessdata);
|
console.log('accessdata: ', this.accessdata);
|
||||||
if (this.accessdata.length == 0) {
|
if(this.accessdata.length==0){
|
||||||
this.error = "No data Available";
|
this.error="No data Available";
|
||||||
console.log(this.error)
|
console.log(this.error)
|
||||||
}
|
}
|
||||||
}, (error) => {
|
},(error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
if (error) {
|
if(error){
|
||||||
this.error = "Server Error";
|
this.error="Server Error";
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
usergrp() {
|
usergrp(){
|
||||||
this.usergrpservice.getAll().subscribe((data) => {
|
this.usergrpservice.getAll().subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.usergrpdata = data;
|
this.usergrpdata = data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getById(id: number) {
|
getById(id:number){
|
||||||
this.mainservice.getbyid(id).subscribe((data) => {
|
this.mainservice.getbyid(id).subscribe((data)=>{
|
||||||
this.data1 = data;
|
this.data1=data;
|
||||||
// this.data1=this.data3;
|
// this.data1=this.data3;
|
||||||
console.log(this.data1);
|
console.log(this.data1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
update() {
|
update(){
|
||||||
console.log(this.data1);
|
console.log(this.data1);
|
||||||
this.mainservice.updatenew(this.id, this.data1).subscribe((data) => {
|
this.mainservice.updatenew(this.id,this.data1).subscribe((data)=>{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
goback() {
|
goback(){
|
||||||
this.router.navigate(["../../usermaintance"], { relativeTo: this.route });
|
this.router.navigate(["../../usermaintance"], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
gotodepartmet() {
|
gotodepartmet(){
|
||||||
this.department = !this.department;
|
this.department=!this.department;
|
||||||
this.mainservice.getalldepartment().subscribe((data) => {
|
this.mainservice.getalldepartment().subscribe((data)=>{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.departmentdata = data;
|
this.departmentdata=data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getdepid(id: number) {
|
getdepid(id:number){
|
||||||
this.clickedID = id;
|
this.clickedID=id;
|
||||||
console.log("clicked by id" + id);
|
console.log("clicked by id"+ id);
|
||||||
this.mainservice.getbydepartmentid(id).subscribe((data) => {
|
this.mainservice.getbydepartmentid(id).subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.deptiddata = data;
|
this.deptiddata= data;
|
||||||
// this.userObj= this.custiddata;
|
// this.userObj= this.custiddata;
|
||||||
this.userobjdept = this.deptiddata;
|
this.userobjdept =this.deptiddata;
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
this.department = false;
|
this.department=false;
|
||||||
}
|
}
|
||||||
gotoposition() {
|
gotoposition(){
|
||||||
this.position = !this.position;
|
this.position=!this.position;
|
||||||
this.mainservice.getallposition().subscribe((data) => {
|
this.mainservice.getallposition().subscribe((data)=>{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.positiondata = data;
|
this.positiondata=data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
getposid(id: number) {
|
getposid(id:number){
|
||||||
this.clickedID = id;
|
this.clickedID=id;
|
||||||
console.log("clicked by id" + id);
|
console.log("clicked by id"+ id);
|
||||||
this.mainservice.getbypositionid(id).subscribe((data) => {
|
this.mainservice.getbypositionid(id).subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.posiddata = data;
|
this.posiddata= data;
|
||||||
// this.userObj= this.custiddata;
|
// this.userObj= this.custiddata;
|
||||||
this.userobjpos = this.posiddata;
|
this.userobjpos =this.posiddata;
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
this.position = false;
|
this.position=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -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;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export const Data_lakecardvariable = {
|
|
||||||
"cardButton": false,
|
|
||||||
"cardmodeldata": ``
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +1,58 @@
|
|||||||
<ol class="breadcrumb breadcrumb-arrow font-trirong">
|
<ol class="breadcrumb breadcrumb-arrow font-trirong">
|
||||||
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
|
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
|
||||||
<li><a href="javascript://"><clr-icon shape="dashboard"></clr-icon> {{ 'Dashboard_builder' | translate }}</a></li>
|
<li><a href="javascript://"><clr-icon shape="dashboard"></clr-icon> {{ 'Dashboard_builder' | translate }}</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="dg-wrapper">
|
<div class="dg-wrapper">
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<div class="clr-col-8">x
|
<div class="clr-col-8">
|
||||||
<h3>{{ 'Dashboard_builder' | translate }}</h3>
|
<h3>{{ 'Dashboard_builder' | translate }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-4" style="text-align: right;">
|
<div class="clr-col-4" style="text-align: right;">
|
||||||
<button class="btn btn-success" [routerLink]="['/cns-portal/shield-dashboard']">
|
<button id="add" class="btn btn-primary" (click)="gotorunner()">
|
||||||
<clr-icon shape="shield"></clr-icon>Shield Dashboard
|
<clr-icon shape="grid-view"></clr-icon>{{ 'Dashboard_runner' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button id="add" class="btn btn-primary" (click)="gotorunner()">
|
<button class="btn btn-outline" (click)="onExport()">
|
||||||
<clr-icon shape="grid-view"></clr-icon>{{ 'Dashboard_runner' | translate }}
|
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<!-- Add Chart Config button -->
|
<button id="add" class="btn btn-primary" (click)="gotoadd()">
|
||||||
<button class="btn btn-primary" (click)="openChartConfig()">
|
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
|
||||||
<clr-icon shape="cog"></clr-icon> Chart Config
|
</button>
|
||||||
</button>
|
</div>
|
||||||
<button class="btn btn-outline" (click)="onExport()">
|
</div>
|
||||||
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
|
|
||||||
</button>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<button id="add" class="btn btn-primary" (click)="gotoadd()">
|
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{ 'Loading' | translate }} ...... </clr-spinner></ng-template>
|
||||||
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
|
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
|
||||||
</button>
|
|
||||||
</div>
|
<clr-dg-column [clrDgField]="''">
|
||||||
</div>
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
{{ 'Go_to' | translate }}
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
</ng-container>
|
||||||
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{ 'Loading' | translate }} ......
|
</clr-dg-column>
|
||||||
</clr-spinner></ng-template>
|
<clr-dg-column [clrDgField]="'dashboard_name'">
|
||||||
<div *ngIf="error;else loadingSpinner">{{error}}</div>
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</clr-dg-placeholder>
|
{{ 'Dashboard_Name' | translate }}
|
||||||
|
</ng-container>
|
||||||
<clr-dg-column [clrDgField]="''">
|
</clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'description'">
|
||||||
{{ 'Go_to' | translate }}
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</ng-container>
|
{{ 'Description' | translate }}
|
||||||
</clr-dg-column>
|
</ng-container>
|
||||||
<clr-dg-column [clrDgField]="'dashboard_name'">
|
</clr-dg-column >
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'secuirity_profile'">
|
||||||
{{ 'Dashboard_Name' | translate }}
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</ng-container>
|
{{ 'Security_Profile' | translate }}
|
||||||
</clr-dg-column>
|
</ng-container>
|
||||||
<clr-dg-column [clrDgField]="'description'">
|
</clr-dg-column >
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'add_to_home'">
|
||||||
{{ 'Description' | translate }}
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
</ng-container>
|
{{ 'Add_to_home' | translate }}
|
||||||
</clr-dg-column>
|
</ng-container>
|
||||||
<clr-dg-column [clrDgField]="'secuirity_profile'">
|
</clr-dg-column >
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<!-- <clr-dg-column [clrDgField]="'formType'">
|
||||||
{{ 'Security_Profile' | translate }}
|
|
||||||
</ng-container>
|
|
||||||
</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 [clrDgField]="'formType'">
|
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
Build Status
|
Build Status
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -71,86 +62,84 @@
|
|||||||
Testing
|
Testing
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</clr-dg-column > -->
|
</clr-dg-column > -->
|
||||||
<clr-dg-column [clrDgField]="'action'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'action'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
{{ 'Action' | translate }}
|
{{ 'Action' | translate }}
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let user of data?.slice()?.reverse();" [clrDgItem]="user">
|
||||||
|
<clr-dg-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>
|
||||||
|
<clr-dg-cell>{{user.add_to_home}}</clr-dg-cell>
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of data?.slice()?.reverse();" [clrDgItem]="user">
|
<!-- <clr-dg-cell><input type="radio" id="cb1" class="dots" [ngStyle]="{'background-color': user.build == true ? 'green' : 'red'}"></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>
|
|
||||||
<clr-dg-cell>{{user.add_to_home}}</clr-dg-cell>
|
|
||||||
|
|
||||||
<!-- <clr-dg-cell><input type="radio" id="cb1" class="dots" [ngStyle]="{'background-color': user.build == true ? 'green' : 'red'}"></clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{user.testing}}</clr-dg-cell> -->
|
<clr-dg-cell>{{user.testing}}</clr-dg-cell> -->
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<!-- <span style="cursor: pointer; padding: 10px; "><clr-icon shape="form" (click)="goToEdit(user.id)" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span> -->
|
<!-- <span style="cursor: pointer; padding: 10px; "><clr-icon shape="form" (click)="goToEdit(user.id)" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span> -->
|
||||||
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true"
|
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true" class="tooltip tooltip-sm tooltip-top-left">
|
||||||
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 style="cursor: pointer;"><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error"
|
<span class="tooltip-content">{{ 'Delete' | translate }}</span>
|
||||||
style="color: red;"></clr-icon></span>
|
</a>
|
||||||
<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>
|
||||||
<clr-signpost>
|
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
||||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success"
|
<h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5>
|
||||||
style="color: rgb(0, 130, 236);"></clr-icon></span>
|
<div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div>
|
||||||
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
<div>{{ 'Created_At' | translate }}: <code class="clr-code">{{ user.createdAt | date }}</code></div>
|
||||||
<h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5>
|
<div>{{ 'Created_By' | translate }}: <code class="clr-code">{{ user.createdBy }}</code></div>
|
||||||
<div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div>
|
<div>{{ 'Updated_At' | translate }}: <code class="clr-code">{{ user.updatedAt | date }}</code></div>
|
||||||
<div>{{ 'Created_At' | translate }}: <code class="clr-code">{{ user.createdAt | date }}</code></div>
|
<div>{{ 'Updated_By' | translate }}: <code class="clr-code">{{ user.updatedBy }}</code></div>
|
||||||
<div>{{ 'Created_By' | translate }}: <code class="clr-code">{{ user.createdBy }}</code></div>
|
</clr-signpost-content>
|
||||||
<div>{{ 'Updated_At' | translate }}: <code class="clr-code">{{ user.updatedAt | date }}</code></div>
|
</clr-signpost>
|
||||||
<div>{{ 'Updated_By' | translate }}: <code class="clr-code">{{ user.updatedBy }}</code></div>
|
|
||||||
</clr-signpost-content>
|
</clr-dg-cell>
|
||||||
</clr-signpost>
|
<clr-dg-action-overflow>
|
||||||
|
<button class="action-item" (click)="goToEditData(user.id)">{{ 'Edit' | translate }} <clr-icon shape="edit" class="is-error"></clr-icon></button>
|
||||||
</clr-dg-cell>
|
<!-- <button class="action-item" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button> -->
|
||||||
<clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="goToEditData(user.id)">{{ 'Edit' | translate }} <clr-icon shape="edit"
|
</clr-dg-row>
|
||||||
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-footer>
|
||||||
</clr-dg-action-overflow>
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
</clr-dg-row>
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{ 'Users_per_page' | translate }}</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
<clr-dg-footer>
|
{{ 'of' | translate }} {{pagination.totalItems}} {{ 'users' | translate }}
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
</clr-dg-pagination>
|
||||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{ 'Users_per_page' | translate }}</clr-dg-page-size>
|
</clr-dg-footer>
|
||||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
</clr-datagrid>
|
||||||
{{ 'of' | translate }} {{pagination.totalItems}} {{ 'users' | translate }}
|
|
||||||
</clr-dg-pagination>
|
|
||||||
</clr-dg-footer>
|
|
||||||
</clr-datagrid>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="addModall" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="s-order-dash-pg">
|
|
||||||
<div class="chart-box" id="word1" (click)="gotoadd()"><br>
|
|
||||||
<img style="margin: auto; display: block;" src="/assets/images/fromscratch.png" height="90" width="90">
|
|
||||||
<h5 class="center"> <b>{{ 'Start_from_scratch' | translate }}</b> </h5>
|
|
||||||
</div>
|
|
||||||
<div class="chart-box" id="word1"><br>
|
|
||||||
<img style="margin: auto; display: block;" src="/assets/images/copytemplate.png" height="90" width="90">
|
|
||||||
<h5 class="center"> <b>{{ 'Import_from_template' | translate }}</b> </h5>
|
|
||||||
</div>
|
|
||||||
<div class="chart-box" id="word1"><br>
|
|
||||||
<img style="margin: auto; display: block;" src="/assets/images/database.png" height="90" width="90">
|
|
||||||
<h5 class="center"> <b>{{ 'Import_from_public_project' | translate }}</b> </h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</clr-modal>
|
<clr-modal [(clrModalOpen)]="addModall" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||||
|
<div class="modal-body">
|
||||||
<clr-modal [(clrModalOpen)]="modalDelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
<div class="s-order-dash-pg">
|
||||||
<div class="modal-body" *ngIf="rowSelected.id">
|
<div class="chart-box" id="word1" (click)="gotoadd()"><br>
|
||||||
<h1 class="delete">{{ 'Are_you_sure_want_to_delete' | translate }}</h1>
|
<img style="margin: auto; display: block;" src="/assets/images/fromscratch.png" height="90" width="90">
|
||||||
<h2 class="heading">{{rowSelected.id}}</h2>
|
<h5 class="center"> <b>{{ 'Start_from_scratch' | translate }}</b> </h5>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button type="button" class="btn btn-outline" (click)="modalDelete = false">{{ 'Cancel' | translate }}</button>
|
<div class="chart-box" id="word1" ><br>
|
||||||
<button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary">{{ 'Delete' | translate }}</button>
|
<img style="margin: auto; display: block;" src="/assets/images/copytemplate.png" height="90" width="90">
|
||||||
</div>
|
<h5 class="center"> <b>{{ 'Import_from_template' | translate }}</b> </h5>
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
<div class="chart-box" id="word1"><br>
|
||||||
|
<img style="margin: auto; display: block;" src="/assets/images/database.png" height="90" width="90">
|
||||||
|
<h5 class="center"> <b>{{ 'Import_from_public_project' | translate }}</b> </h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-modal>
|
||||||
|
|
||||||
|
<clr-modal [(clrModalOpen)]="modalDelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||||
|
<div class="modal-body" *ngIf="rowSelected.id">
|
||||||
|
<h1 class="delete">{{ 'Are_you_sure_want_to_delete' | translate }}</h1>
|
||||||
|
<h2 class="heading">{{rowSelected.id}}</h2>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline" (click)="modalDelete = false">{{ 'Cancel' | translate }}</button>
|
||||||
|
<button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary" >{{ 'Delete' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-modal>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ export class AllnewdashComponent implements OnInit {
|
|||||||
projectname;
|
projectname;
|
||||||
projectId;
|
projectId;
|
||||||
error;
|
error;
|
||||||
chartConfigManagerOpen = false;
|
|
||||||
constructor(
|
constructor(
|
||||||
private router : Router,
|
private router : Router,
|
||||||
private route: ActivatedRoute,private dashboardService : DashboardService,
|
private route: ActivatedRoute,private dashboardService : DashboardService,
|
||||||
@@ -122,9 +121,4 @@ export class AllnewdashComponent implements OnInit {
|
|||||||
// this.router.navigate(['../editdashn'],{relativeTo:this.route});
|
// this.router.navigate(['../editdashn'],{relativeTo:this.route});
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Add method to open chart configuration manager
|
|
||||||
openChartConfig(): void {
|
|
||||||
this.router.navigate(['../chart-types'],{relativeTo:this.route});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 { }
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
Reference in New Issue
Block a user