feat: edit client — save per tab, DOB re-KYC alert, audit logs, KYC improvements
- Edit button per tab with Save & Next confirmation dialog - DOB change triggers re-KYC alert, resets PAN verification, auto-fills remarks - Mobile/email changes logged as audit notes via client notes API - Redesigned confirmation dialog with icon + title, type-based theming - KYC re-edit flow: remarks mandatory for VERIFIED records, re-edit API command - Load existing photo & signature in edit KYC, immediate upload on change - Populate all KYC fields (document, AML, form60/15) from API on edit - Non-individual PAN verification uses same flow as individual - Fix table overflow when sidebar panel is open - Add fr unit to stylelint allowed-list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b890d24bb7
commit
79e455c4ed
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges, inject } from '@angular/core';
|
||||
@@ -54,77 +58,88 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
kycId: string = '';
|
||||
/** Whether existing KYC data has been loaded (edit mode) */
|
||||
kycLoaded = false;
|
||||
/** KYC status from API: 100=PENDING, 200=IN_PROGRESS, 300=VERIFIED */
|
||||
kycStatus: number = 0;
|
||||
/** Whether the KYC record is in VERIFIED state (needs re-edit command) */
|
||||
get isKycVerified(): boolean {
|
||||
return this.kycStatus === 300;
|
||||
}
|
||||
|
||||
/** KYC Document Types — populated from API template, falls back to defaults */
|
||||
kycDocumentTypes: { name: string; code: string; mandatory: boolean }[] = [];
|
||||
/** Fallback document types when template API is unavailable */
|
||||
private defaultDocumentTypes = [
|
||||
'Aadhaar Card', 'Passport', 'PAN Card', 'Driving Licence',
|
||||
'Voter ID', 'NREGA Job Card', 'National Population Register Letter'
|
||||
'Aadhaar Card',
|
||||
'Passport',
|
||||
'PAN Card',
|
||||
'Driving Licence',
|
||||
'Voter ID',
|
||||
'NREGA Job Card',
|
||||
'National Population Register Letter'
|
||||
];
|
||||
/** Constitution-based KYC document types for non-individual clients */
|
||||
private constitutionDocTypes: Record<string, { name: string; mandatory: boolean }[]> = {
|
||||
'HUF': [
|
||||
HUF: [
|
||||
{ name: 'PAN Card of HUF', mandatory: true },
|
||||
{ name: 'Declaration by Karta (with name, address & HUF member details)', mandatory: true },
|
||||
{ name: 'KYC of Karta (OVDs + PAN + Photo)', mandatory: true },
|
||||
{ name: 'Proof of Address of HUF', mandatory: false }
|
||||
],
|
||||
'PRIVATE_LIMITED': [
|
||||
PRIVATE_LIMITED: [
|
||||
{ name: 'Certificate of Incorporation', mandatory: true },
|
||||
{ name: 'Memorandum & Articles of Association', mandatory: true },
|
||||
{ name: 'PAN of Company', mandatory: true },
|
||||
{ name: 'Board Resolution (authorizing signatories)', mandatory: true },
|
||||
{ name: 'KYC of Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'PUBLIC_LIMITED': [
|
||||
PUBLIC_LIMITED: [
|
||||
{ name: 'Certificate of Incorporation', mandatory: true },
|
||||
{ name: 'Memorandum & Articles of Association', mandatory: true },
|
||||
{ name: 'PAN of Company', mandatory: true },
|
||||
{ name: 'Board Resolution (authorizing signatories)', mandatory: true },
|
||||
{ name: 'KYC of Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'LLP': [
|
||||
LLP: [
|
||||
{ name: 'Certificate of Incorporation', mandatory: true },
|
||||
{ name: 'LLP Agreement', mandatory: true },
|
||||
{ name: 'PAN of LLP', mandatory: true },
|
||||
{ name: 'Designated Partners Resolution', mandatory: true },
|
||||
{ name: 'KYC of Designated Partners / Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'PARTNERSHIP': [
|
||||
PARTNERSHIP: [
|
||||
{ name: 'Registration Certificate', mandatory: false },
|
||||
{ name: 'Partnership Deed', mandatory: true },
|
||||
{ name: 'PAN of Firm', mandatory: true },
|
||||
{ name: 'KYC of Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'TRUST': [
|
||||
TRUST: [
|
||||
{ name: 'Registration Certificate', mandatory: true },
|
||||
{ name: 'Trust Deed', mandatory: true },
|
||||
{ name: 'PAN of Trust', mandatory: true },
|
||||
{ name: 'KYC of Trustees / Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'SOCIETY': [
|
||||
SOCIETY: [
|
||||
{ name: 'Registration Certificate', mandatory: true },
|
||||
{ name: 'PAN of Society', mandatory: true },
|
||||
{ name: 'KYC of Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'PROPRIETORSHIP': [
|
||||
PROPRIETORSHIP: [
|
||||
{ name: 'Proof of Business Existence (GST Certificate / Shop License)', mandatory: true },
|
||||
{ name: 'PAN of Proprietor', mandatory: true },
|
||||
{ name: "Proprietor's KYC (OVDs + PAN + Photo)", mandatory: true }
|
||||
],
|
||||
'ASSOCIATION': [
|
||||
ASSOCIATION: [
|
||||
{ name: 'Registration Certificate', mandatory: true },
|
||||
{ name: 'PAN', mandatory: true },
|
||||
{ name: 'Resolution authorizing signatories', mandatory: true },
|
||||
{ name: 'KYC of Authorized Signatories', mandatory: true }
|
||||
],
|
||||
'GOVT_PSU': [
|
||||
GOVT_PSU: [
|
||||
{ name: 'Government Notification / Order', mandatory: true },
|
||||
{ name: 'PAN (if applicable)', mandatory: false },
|
||||
{ name: 'Authorized Signatory KYC', mandatory: true }
|
||||
],
|
||||
'COOPERATIVE': [
|
||||
COOPERATIVE: [
|
||||
{ name: 'Registration Certificate', mandatory: true },
|
||||
{ name: 'PAN', mandatory: true },
|
||||
{ name: 'Resolution authorizing signatories', mandatory: true },
|
||||
@@ -161,7 +176,14 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
aadhaarOtpReferenceId = '';
|
||||
|
||||
/** OTP digit boxes */
|
||||
otpDigits: string[] = ['', '', '', '', '', ''];
|
||||
otpDigits: string[] = [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
''
|
||||
];
|
||||
/** Resend countdown */
|
||||
resendCountdown = 0;
|
||||
private resendTimer: any = null;
|
||||
@@ -221,7 +243,14 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
resetOtpDigits(): void {
|
||||
this.otpDigits = ['', '', '', '', '', ''];
|
||||
this.otpDigits = [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
''
|
||||
];
|
||||
this.aadhaarOtp = '';
|
||||
}
|
||||
|
||||
@@ -254,25 +283,73 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
photographPreview: string | null = null;
|
||||
signatureFile: File | null = null;
|
||||
signaturePreview: string | null = null;
|
||||
/** Existing signature document ID (for re-upload/delete) */
|
||||
existingSignatureDocId: string = '';
|
||||
|
||||
/** KYC Form */
|
||||
kycForm: UntypedFormGroup = this.formBuilder.group({
|
||||
hasPan: [true],
|
||||
panNumber: ['', [Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/), Validators.maxLength(10)]],
|
||||
aadhaarNumber: ['', [Validators.pattern(/^\d{12}$/), Validators.maxLength(12)]],
|
||||
panNumber: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/),
|
||||
Validators.maxLength(10)
|
||||
]
|
||||
],
|
||||
aadhaarNumber: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^\d{12}$/),
|
||||
Validators.maxLength(12)
|
||||
]
|
||||
],
|
||||
panAadhaarSeedingStatus: [''],
|
||||
gstinNumber: ['', [Validators.pattern(/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/), Validators.maxLength(15)]],
|
||||
enterprisePan: ['', [Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/), Validators.maxLength(10)]],
|
||||
cinNumber: ['', [Validators.pattern(/^[A-Z]{1}[0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$/), Validators.maxLength(21)]],
|
||||
gstinNumber: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/),
|
||||
Validators.maxLength(15)
|
||||
]
|
||||
],
|
||||
enterprisePan: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/),
|
||||
Validators.maxLength(10)
|
||||
]
|
||||
],
|
||||
cinNumber: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^[A-Z]{1}[0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$/),
|
||||
Validators.maxLength(21)
|
||||
]
|
||||
],
|
||||
incorporationDate: [''],
|
||||
kycDocumentName: ['', Validators.required],
|
||||
kycDocumentNumber: ['', [Validators.required, Validators.pattern(/^[A-Za-z0-9\-\/\s]+$/), Validators.maxLength(30)]],
|
||||
kycDocumentName: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
kycDocumentNumber: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern(/^[A-Za-z0-9\-\/\s]+$/),
|
||||
Validators.maxLength(30)
|
||||
]
|
||||
],
|
||||
kycDocIssueDate: [''],
|
||||
kycDocExpiryDate: [''],
|
||||
riskCategory: [1],
|
||||
remarks: [''],
|
||||
amlCheckStatus: ['CLEAR', Validators.required],
|
||||
bannedEntityMatch: ['NO', Validators.required],
|
||||
amlCheckStatus: [
|
||||
'CLEAR',
|
||||
Validators.required
|
||||
],
|
||||
bannedEntityMatch: [
|
||||
'NO',
|
||||
Validators.required
|
||||
],
|
||||
willfulDefaulter: ['NO'],
|
||||
ecgcSalFlag: ['NO'],
|
||||
dormantFlag: ['ACTIVE'],
|
||||
@@ -320,7 +397,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private updateDocTypesForConstitution(): void {
|
||||
const constitutionValue = this.constitutionMeta?.value;
|
||||
if (constitutionValue && this.constitutionDocTypes[constitutionValue]) {
|
||||
this.kycDocumentTypes = this.constitutionDocTypes[constitutionValue].map(doc => ({
|
||||
this.kycDocumentTypes = this.constitutionDocTypes[constitutionValue].map((doc) => ({
|
||||
name: doc.name,
|
||||
code: doc.name,
|
||||
mandatory: doc.mandatory
|
||||
@@ -339,13 +416,51 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.kycLoaded = true;
|
||||
if (data) {
|
||||
this.kycId = data.id?.toString() || '';
|
||||
this.kycStatus = data.status?.id || data.kycStatus || 0;
|
||||
// Helper to parse API date arrays [y,m,d] or strings into Date objects
|
||||
const parseDate = (val: any): Date | string => {
|
||||
if (Array.isArray(val) && val.length >= 3) {
|
||||
return new Date(val[0], val[1] - 1, val[2]);
|
||||
}
|
||||
if (val && typeof val === 'string') {
|
||||
const d = new Date(val);
|
||||
return isNaN(d.getTime()) ? '' : d;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
this.kycForm.patchValue({
|
||||
panNumber: data.panNumber || '',
|
||||
aadhaarNumber: data.aadhaarNumberMasked || '',
|
||||
gstinNumber: data.gstinNumber || '',
|
||||
enterprisePan: data.enterprisePan || '',
|
||||
cinNumber: data.cinNumber || '',
|
||||
incorporationDate: parseDate(data.incorporationDate),
|
||||
// KYC Document
|
||||
kycDocumentName: data.kycDocumentName || '',
|
||||
kycDocumentNumber: data.kycDocumentNumber || '',
|
||||
kycDocIssueDate: parseDate(data.kycDocIssueDate),
|
||||
kycDocExpiryDate: parseDate(data.kycDocExpiryDate),
|
||||
// Risk & Remarks
|
||||
riskCategory: data.riskCategory || 1,
|
||||
remarks: data.remarks || '',
|
||||
hasPan: !!data.panNumber
|
||||
hasPan: !!data.panNumber,
|
||||
// AML & Compliance
|
||||
amlCheckStatus: data.amlCheckStatus || 'CLEAR',
|
||||
bannedEntityMatch: data.bannedEntityMatch || 'NO',
|
||||
willfulDefaulter: data.willfulDefaulter || 'NO',
|
||||
ecgcSalFlag: data.ecgcSalFlag || 'NO',
|
||||
dormantFlag: data.dormantFlag || 'ACTIVE',
|
||||
// Account Status & Freeze
|
||||
accountStatus: data.accountStatus || 'ACTIVE',
|
||||
freezeReason: data.freezeReason || '',
|
||||
freezeAuthority: data.freezeAuthority || '',
|
||||
// Form 60 / Form 15
|
||||
form60Type: data.form60Type || 'FORM_60',
|
||||
form60Date: parseDate(data.form60Date),
|
||||
form15Applicable: data.form15Applicable || false,
|
||||
form15Type: data.form15Type || '15G',
|
||||
form15FY: data.form15FY || ''
|
||||
});
|
||||
// Set verification statuses from existing data
|
||||
if (data.panVerified) {
|
||||
@@ -366,6 +481,9 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.panAadhaarSeedingStatus = data.panAadhaarSeedingStatus;
|
||||
}
|
||||
}
|
||||
// Load existing photo & signature
|
||||
this.loadExistingPhoto();
|
||||
this.loadExistingSignature();
|
||||
},
|
||||
error: () => {
|
||||
// No existing KYC — that's fine for new clients
|
||||
@@ -374,6 +492,45 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/** Fetch existing client profile photo */
|
||||
private loadExistingPhoto(): void {
|
||||
if (!this.clientId) return;
|
||||
this.clientsService.getClientProfileImage(this.clientId).subscribe({
|
||||
next: (imageData: any) => {
|
||||
if (imageData) {
|
||||
this.photographPreview = imageData;
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
/** Fetch existing client signature from documents */
|
||||
private loadExistingSignature(): void {
|
||||
if (!this.clientId) return;
|
||||
this.clientsService.getClientDocuments(this.clientId).subscribe({
|
||||
next: (documents: any[]) => {
|
||||
const sigDoc = documents?.find((d: any) => d.name === 'clientSignature');
|
||||
if (sigDoc?.id) {
|
||||
this.existingSignatureDocId = sigDoc.id.toString();
|
||||
this.clientsService.getClientSignatureImage(this.clientId, this.existingSignatureDocId).subscribe({
|
||||
next: async (blob: any) => {
|
||||
const buffer = await blob.arrayBuffer();
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
this.signaturePreview = 'data:' + blob.type + ';base64,' + btoa(binary);
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
/** Load KYC template to get required documents based on client type */
|
||||
private loadKycTemplate(): void {
|
||||
if (!this.clientId || this.kycTemplateLoaded) return;
|
||||
@@ -388,7 +545,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
mandatory: doc.mandatory ?? false
|
||||
}));
|
||||
// Update kycDocumentName validator based on mandatory docs
|
||||
const hasMandatory = this.kycDocumentTypes.some(d => d.mandatory);
|
||||
const hasMandatory = this.kycDocumentTypes.some((d) => d.mandatory);
|
||||
if (hasMandatory) {
|
||||
this.kycForm.get('kycDocumentName')?.setValidators(Validators.required);
|
||||
}
|
||||
@@ -398,7 +555,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
error: () => {
|
||||
// Fallback to defaults if template API unavailable
|
||||
this.kycTemplateLoaded = true;
|
||||
this.kycDocumentTypes = this.defaultDocumentTypes.map(name => ({
|
||||
this.kycDocumentTypes = this.defaultDocumentTypes.map((name) => ({
|
||||
name,
|
||||
code: name,
|
||||
mandatory: false
|
||||
@@ -410,19 +567,48 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
/** Save or update KYC, then execute callback */
|
||||
private saveKycAndDo(payload: any, callback: () => void, onError: (msg: string) => void) {
|
||||
if (this.kycId) {
|
||||
// KYC already exists — use PUT to update (strip create-only fields)
|
||||
const { kycType, ...updatePayload } = payload;
|
||||
this.clientsService.updateClientKyc(this.clientId, this.kycId, updatePayload).subscribe({
|
||||
next: () => callback(),
|
||||
error: (err: any) => onError(err?.error?.defaultUserMessage || 'Failed to update KYC.')
|
||||
});
|
||||
|
||||
// If KYC is VERIFIED, remarks are mandatory — use re-edit command
|
||||
if (this.isKycVerified) {
|
||||
const remarks = this.kycForm.get('remarks')?.value?.trim();
|
||||
if (!remarks) {
|
||||
onError('Remarks are mandatory when updating a verified KYC record.');
|
||||
this.kycForm.get('remarks')?.markAsTouched();
|
||||
return;
|
||||
}
|
||||
updatePayload.remarks = remarks;
|
||||
this.clientsService.reEditClientKyc(this.clientId, this.kycId, updatePayload).subscribe({
|
||||
next: () => {
|
||||
// Status transitions to IN_PROGRESS, PAN verification is invalidated
|
||||
this.kycStatus = 200;
|
||||
this.panVerifyStatus = 'NONE';
|
||||
this.panVerifyName = '';
|
||||
this.panVerifiedOn = '';
|
||||
callback();
|
||||
},
|
||||
error: (err: any) =>
|
||||
onError(
|
||||
err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || 'Failed to re-edit KYC.'
|
||||
)
|
||||
});
|
||||
} else {
|
||||
// Normal update for non-verified records
|
||||
this.clientsService.updateClientKyc(this.clientId, this.kycId, updatePayload).subscribe({
|
||||
next: () => callback(),
|
||||
error: (err: any) => onError(err?.error?.defaultUserMessage || 'Failed to update KYC.')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// First time — use POST to create
|
||||
this.clientsService.saveClientKyc(this.clientId, payload).subscribe({
|
||||
next: (res: any) => {
|
||||
this.kycId = res?.resourceId?.toString() || '';
|
||||
if (this.kycId) { callback(); }
|
||||
else { onError('Failed to save KYC.'); }
|
||||
if (this.kycId) {
|
||||
callback();
|
||||
} else {
|
||||
onError('Failed to save KYC.');
|
||||
}
|
||||
},
|
||||
error: (err: any) => onError(err?.error?.defaultUserMessage || 'Failed to save KYC.')
|
||||
});
|
||||
@@ -436,7 +622,10 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return;
|
||||
}
|
||||
const pan = this.kycForm.value.panNumber;
|
||||
if (!pan) { this.panVerifyError = 'Enter PAN number first.'; return; }
|
||||
if (!pan) {
|
||||
this.panVerifyError = 'Enter PAN number first.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.panVerifying = true;
|
||||
this.panVerifyError = '';
|
||||
@@ -451,107 +640,136 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
aadhaarNumberMasked: aadhaarRaw.length === 12 ? 'XXXXXXXX' + aadhaarRaw.slice(-4) : 'XXXXXXXX0000'
|
||||
};
|
||||
|
||||
this.saveKycAndDo(panPayload, () => {
|
||||
this.clientsService.verifyPAN(this.clientId, this.kycId).subscribe({
|
||||
next: (res: any) => {
|
||||
this.panVerifying = false;
|
||||
if (res?.changes?.panVerified === true) {
|
||||
// Full name from API
|
||||
const resolvedName = res.changes?.panFullName
|
||||
|| res.changes?.panHolderName
|
||||
|| [res.changes?.panFirstName, res.changes?.panLastName].filter(Boolean).join(' ')
|
||||
|| '';
|
||||
this.saveKycAndDo(
|
||||
panPayload,
|
||||
() => {
|
||||
this.clientsService.verifyPAN(this.clientId, this.kycId).subscribe({
|
||||
next: (res: any) => {
|
||||
this.panVerifying = false;
|
||||
if (res?.changes?.panVerified === true) {
|
||||
// Full name from API
|
||||
const resolvedName =
|
||||
res.changes?.panFullName ||
|
||||
res.changes?.panHolderName ||
|
||||
[
|
||||
res.changes?.panFirstName,
|
||||
res.changes?.panLastName
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ') ||
|
||||
'';
|
||||
|
||||
// If name is empty, the client name/DOB didn't match PAN holder
|
||||
if (!resolvedName || !resolvedName.trim()) {
|
||||
this.panVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert('PAN verification failed: Client name or date of birth does not match the PAN card holder. Please ensure the client\'s name and date of birth exactly match the PAN card details.');
|
||||
return;
|
||||
}
|
||||
// If name is empty, the client name/DOB didn't match PAN holder
|
||||
if (!resolvedName || !resolvedName.trim()) {
|
||||
this.panVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(
|
||||
"PAN verification failed: Client name or date of birth does not match the PAN card holder. Please ensure the client's name and date of birth exactly match the PAN card details."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.panVerifyStatus = 'VERIFIED';
|
||||
this.panVerifyName = resolvedName;
|
||||
// Date — could be string or array [y,m,d]
|
||||
const rawDate = res.changes?.panVerifiedOn;
|
||||
if (Array.isArray(rawDate)) {
|
||||
this.panVerifiedOn = `${rawDate[2]?.toString().padStart(2, '0')}/${rawDate[1]?.toString().padStart(2, '0')}/${rawDate[0]}`;
|
||||
} else if (rawDate) {
|
||||
this.panVerifiedOn = rawDate;
|
||||
this.panVerifyStatus = 'VERIFIED';
|
||||
this.panVerifyName = resolvedName;
|
||||
// Date — could be string or array [y,m,d]
|
||||
const rawDate = res.changes?.panVerifiedOn;
|
||||
if (Array.isArray(rawDate)) {
|
||||
this.panVerifiedOn = `${rawDate[2]?.toString().padStart(2, '0')}/${rawDate[1]?.toString().padStart(2, '0')}/${rawDate[0]}`;
|
||||
} else if (rawDate) {
|
||||
this.panVerifiedOn = rawDate;
|
||||
} else {
|
||||
const now = new Date();
|
||||
this.panVerifiedOn = `${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`;
|
||||
}
|
||||
// Seeding status
|
||||
if (res.changes?.panAadhaarSeedingStatus) {
|
||||
this.panAadhaarSeedingStatus = res.changes.panAadhaarSeedingStatus;
|
||||
}
|
||||
} else {
|
||||
const now = new Date();
|
||||
this.panVerifiedOn = `${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`;
|
||||
this.panVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(res?.changes?.panVerificationMessage || 'PAN verification failed.');
|
||||
}
|
||||
// Seeding status
|
||||
if (res.changes?.panAadhaarSeedingStatus) {
|
||||
this.panAadhaarSeedingStatus = res.changes.panAadhaarSeedingStatus;
|
||||
}
|
||||
} else {
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.panVerifying = false;
|
||||
this.panVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(res?.changes?.panVerificationMessage || 'PAN verification failed.');
|
||||
const msg =
|
||||
err?.error?.errors?.[0]?.defaultUserMessage ||
|
||||
err?.error?.defaultUserMessage ||
|
||||
'PAN verification failed.';
|
||||
this.showPanErrorAlert(msg);
|
||||
}
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.panVerifying = false;
|
||||
this.panVerifyStatus = 'FAILED';
|
||||
const msg = err?.error?.errors?.[0]?.defaultUserMessage
|
||||
|| err?.error?.defaultUserMessage
|
||||
|| 'PAN verification failed.';
|
||||
this.showPanErrorAlert(msg);
|
||||
}
|
||||
});
|
||||
}, (msg) => { this.panVerifying = false; this.showPanErrorAlert(msg); });
|
||||
});
|
||||
},
|
||||
(msg) => {
|
||||
this.panVerifying = false;
|
||||
this.showPanErrorAlert(msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Verify Enterprise PAN (non-individual) — uses same verify-pan API */
|
||||
/**
|
||||
* Verify Enterprise PAN (non-individual) — same flow as individual PAN verification.
|
||||
* Maps: Company PAN → panNumber, Company Name → client name, Date of Incorporation → DOB
|
||||
*/
|
||||
verifyEnterprisePan() {
|
||||
if (!this.clientId) return;
|
||||
const pan = this.kycForm.value.enterprisePan;
|
||||
if (!pan || this.kycForm.get('enterprisePan')?.invalid) return;
|
||||
|
||||
this.entPanVerifying = true;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const incorpDate = this.kycForm.value.incorporationDate;
|
||||
|
||||
// Same payload structure as individual — backend uses panNumber + client name/DOB for verification
|
||||
const payload: any = {
|
||||
locale: 'en',
|
||||
kycType: 1,
|
||||
riskCategory: this.kycForm.value.riskCategory || 1,
|
||||
panNumber: pan,
|
||||
dateFormat: this.settingsService.dateFormat
|
||||
panNumber: pan, // Company PAN → panNumber
|
||||
dateFormat,
|
||||
aadhaarNumberMasked: 'XXXXXXXX0000' // Placeholder — not applicable for entities
|
||||
};
|
||||
if (incorpDate) {
|
||||
payload.dateOfIncorporation = incorpDate instanceof Date
|
||||
? this.dateUtils.formatDate(incorpDate, this.settingsService.dateFormat)
|
||||
: incorpDate;
|
||||
}
|
||||
if (this.entityName) {
|
||||
payload.companyFullName = this.entityName;
|
||||
}
|
||||
|
||||
this.saveKycAndDo(payload, () => {
|
||||
this.clientsService.verifyPAN(this.clientId, this.kycId).subscribe({
|
||||
next: (res: any) => {
|
||||
this.entPanVerifying = false;
|
||||
if (res?.changes?.panVerified === true) {
|
||||
this.entPanVerifyStatus = 'VERIFIED';
|
||||
this.entPanVerifyName = res.changes?.panFullName || res.changes?.panHolderName || '';
|
||||
const rawDate = res.changes?.panVerifiedOn;
|
||||
if (Array.isArray(rawDate)) {
|
||||
this.entPanVerifiedOn = `${rawDate[2]?.toString().padStart(2, '0')}/${rawDate[1]?.toString().padStart(2, '0')}/${rawDate[0]}`;
|
||||
this.saveKycAndDo(
|
||||
payload,
|
||||
() => {
|
||||
this.clientsService.verifyPAN(this.clientId, this.kycId).subscribe({
|
||||
next: (res: any) => {
|
||||
this.entPanVerifying = false;
|
||||
if (res?.changes?.panVerified === true) {
|
||||
this.entPanVerifyStatus = 'VERIFIED';
|
||||
this.entPanVerifyName = res.changes?.panFullName || res.changes?.panHolderName || '';
|
||||
const rawDate = res.changes?.panVerifiedOn;
|
||||
if (Array.isArray(rawDate)) {
|
||||
this.entPanVerifiedOn = `${rawDate[2]?.toString().padStart(2, '0')}/${rawDate[1]?.toString().padStart(2, '0')}/${rawDate[0]}`;
|
||||
} else {
|
||||
const now = new Date();
|
||||
this.entPanVerifiedOn =
|
||||
rawDate ||
|
||||
`${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`;
|
||||
}
|
||||
} else {
|
||||
const now = new Date();
|
||||
this.entPanVerifiedOn = rawDate || `${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`;
|
||||
this.entPanVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(
|
||||
res?.changes?.panVerificationMessage ||
|
||||
'Enterprise PAN verification failed. Ensure company name and date of incorporation match the PAN records.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.entPanVerifying = false;
|
||||
this.entPanVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(res?.changes?.panVerificationMessage || 'Enterprise PAN verification failed.');
|
||||
this.showPanErrorAlert(
|
||||
err?.error?.errors?.[0]?.defaultUserMessage || 'Enterprise PAN verification failed.'
|
||||
);
|
||||
}
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.entPanVerifying = false;
|
||||
this.entPanVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(err?.error?.errors?.[0]?.defaultUserMessage || 'Enterprise PAN verification failed.');
|
||||
}
|
||||
});
|
||||
}, (msg) => { this.entPanVerifying = false; this.showPanErrorAlert(msg); });
|
||||
});
|
||||
},
|
||||
(msg) => {
|
||||
this.entPanVerifying = false;
|
||||
this.showPanErrorAlert(msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Verify CIN (dummy — no real API) */
|
||||
@@ -563,7 +781,12 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.cinProgressValue = 0;
|
||||
this.cinCurrentStep = 0;
|
||||
|
||||
const stepDurations = [800, 1000, 1200, 800];
|
||||
const stepDurations = [
|
||||
800,
|
||||
1000,
|
||||
1200,
|
||||
800
|
||||
];
|
||||
const totalDuration = stepDurations.reduce((a, b) => a + b, 0);
|
||||
let elapsed = 0;
|
||||
const interval = 50;
|
||||
@@ -573,7 +796,10 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
let accum = 0;
|
||||
for (let i = 0; i < stepDurations.length; i++) {
|
||||
accum += stepDurations[i];
|
||||
if (elapsed <= accum) { this.cinCurrentStep = i; break; }
|
||||
if (elapsed <= accum) {
|
||||
this.cinCurrentStep = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.cinProgressValue = Math.min(Math.round((elapsed / totalDuration) * 100), 100);
|
||||
|
||||
@@ -598,7 +824,10 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return;
|
||||
}
|
||||
const aadhaar = this.kycForm.value.aadhaarNumber;
|
||||
if (!aadhaar || aadhaar.length !== 12) { this.aadhaarVerifyError = 'Enter valid 12-digit Aadhaar first.'; return; }
|
||||
if (!aadhaar || aadhaar.length !== 12) {
|
||||
this.aadhaarVerifyError = 'Enter valid 12-digit Aadhaar first.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.aadhaarVerifying = true;
|
||||
this.aadhaarVerifyError = '';
|
||||
@@ -611,23 +840,32 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
aadhaarNumberMasked: aadhaarMasked
|
||||
};
|
||||
const panVal = this.kycForm.value.panNumber;
|
||||
if (panVal) { aadhaarPayload.panNumber = panVal; }
|
||||
if (panVal) {
|
||||
aadhaarPayload.panNumber = panVal;
|
||||
}
|
||||
|
||||
this.saveKycAndDo(aadhaarPayload, () => {
|
||||
this.clientsService.sendAadhaarOtp(this.clientId, this.kycId, aadhaar).subscribe({
|
||||
next: (res: any) => {
|
||||
this.aadhaarVerifying = false;
|
||||
this.aadhaarOtpSent = true;
|
||||
this.aadhaarOtpReferenceId = res?.changes?.aadhaarRefNumber || '';
|
||||
this.resetOtpDigits();
|
||||
this.startResendCountdown();
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.aadhaarVerifying = false;
|
||||
this.aadhaarVerifyError = err?.error?.defaultUserMessage || 'Failed to send OTP.';
|
||||
}
|
||||
});
|
||||
}, (msg) => { this.aadhaarVerifying = false; this.aadhaarVerifyError = msg; });
|
||||
this.saveKycAndDo(
|
||||
aadhaarPayload,
|
||||
() => {
|
||||
this.clientsService.sendAadhaarOtp(this.clientId, this.kycId, aadhaar).subscribe({
|
||||
next: (res: any) => {
|
||||
this.aadhaarVerifying = false;
|
||||
this.aadhaarOtpSent = true;
|
||||
this.aadhaarOtpReferenceId = res?.changes?.aadhaarRefNumber || '';
|
||||
this.resetOtpDigits();
|
||||
this.startResendCountdown();
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.aadhaarVerifying = false;
|
||||
this.aadhaarVerifyError = err?.error?.defaultUserMessage || 'Failed to send OTP.';
|
||||
}
|
||||
});
|
||||
},
|
||||
(msg) => {
|
||||
this.aadhaarVerifying = false;
|
||||
this.aadhaarVerifyError = msg;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Verify Aadhaar OTP */
|
||||
@@ -636,25 +874,27 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.aadhaarOtpVerifying = true;
|
||||
this.aadhaarVerifyError = '';
|
||||
|
||||
this.clientsService.verifyAadhaarOtp(this.clientId, this.kycId, this.aadhaarOtp, this.aadhaarOtpReferenceId).subscribe({
|
||||
next: (res: any) => {
|
||||
this.aadhaarOtpVerifying = false;
|
||||
if (res?.changes?.aadhaarVerified === true) {
|
||||
this.aadhaarVerifyStatus = 'VERIFIED';
|
||||
this.aadhaarVerifyName = res.changes?.aadhaarHolderName || 'Aadhaar Verified';
|
||||
this.aadhaarOtpSent = false;
|
||||
if (res.changes?.panAadhaarSeedingStatus) {
|
||||
this.panAadhaarSeedingStatus = res.changes.panAadhaarSeedingStatus;
|
||||
this.clientsService
|
||||
.verifyAadhaarOtp(this.clientId, this.kycId, this.aadhaarOtp, this.aadhaarOtpReferenceId)
|
||||
.subscribe({
|
||||
next: (res: any) => {
|
||||
this.aadhaarOtpVerifying = false;
|
||||
if (res?.changes?.aadhaarVerified === true) {
|
||||
this.aadhaarVerifyStatus = 'VERIFIED';
|
||||
this.aadhaarVerifyName = res.changes?.aadhaarHolderName || 'Aadhaar Verified';
|
||||
this.aadhaarOtpSent = false;
|
||||
if (res.changes?.panAadhaarSeedingStatus) {
|
||||
this.panAadhaarSeedingStatus = res.changes.panAadhaarSeedingStatus;
|
||||
}
|
||||
} else {
|
||||
this.aadhaarVerifyError = res?.changes?.aadhaarVerificationMessage || 'OTP verification failed';
|
||||
}
|
||||
} else {
|
||||
this.aadhaarVerifyError = res?.changes?.aadhaarVerificationMessage || 'OTP verification failed';
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.aadhaarOtpVerifying = false;
|
||||
this.aadhaarVerifyError = err?.error?.defaultUserMessage || 'OTP verification failed.';
|
||||
}
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.aadhaarOtpVerifying = false;
|
||||
this.aadhaarVerifyError = err?.error?.defaultUserMessage || 'OTP verification failed.';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private showPanErrorAlert(message: string) {
|
||||
@@ -667,17 +907,34 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
getRenewalYears(): number {
|
||||
switch (this.kycForm.value.riskCategory) {
|
||||
case 1: return 10;
|
||||
case 2: return 8;
|
||||
case 3: return 2;
|
||||
default: return 10;
|
||||
case 1:
|
||||
return 10;
|
||||
case 2:
|
||||
return 8;
|
||||
case 3:
|
||||
return 2;
|
||||
default:
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
getKycExpiryDate(years: number): string {
|
||||
const d = new Date();
|
||||
d.setFullYear(d.getFullYear() + years);
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
];
|
||||
return `${String(d.getDate()).padStart(2, '0')} ${months[d.getMonth()]} ${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
@@ -688,11 +945,21 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
input.value = '';
|
||||
const { ImageCropDialogComponent } = await import('./image-crop-dialog/image-crop-dialog.component');
|
||||
const dialogRef = this.dialog.open(ImageCropDialogComponent, {
|
||||
width: '680px', maxWidth: '95vw',
|
||||
width: '680px',
|
||||
maxWidth: '95vw',
|
||||
data: { file, title: 'Crop Client Photograph', aspectRatio: 1, maxWidth: 300, maxHeight: 300 }
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((result: any) => {
|
||||
if (result) { this.photographFile = result.croppedFile; this.photographPreview = result.previewUrl; }
|
||||
if (result) {
|
||||
this.photographFile = result.croppedFile;
|
||||
this.photographPreview = result.previewUrl;
|
||||
// In edit mode, upload immediately
|
||||
if (this.clientId) {
|
||||
this.clientsService
|
||||
.uploadClientProfileImage(this.clientId, result.croppedFile)
|
||||
.subscribe({ error: () => {} });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -703,16 +970,70 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
input.value = '';
|
||||
const { ImageCropDialogComponent } = await import('./image-crop-dialog/image-crop-dialog.component');
|
||||
const dialogRef = this.dialog.open(ImageCropDialogComponent, {
|
||||
width: '680px', maxWidth: '95vw',
|
||||
width: '680px',
|
||||
maxWidth: '95vw',
|
||||
data: { file, title: 'Crop Specimen Signature', aspectRatio: 2.5, maxWidth: 400, maxHeight: 160 }
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((result: any) => {
|
||||
if (result) { this.signatureFile = result.croppedFile; this.signaturePreview = result.previewUrl; }
|
||||
if (result) {
|
||||
this.signatureFile = result.croppedFile;
|
||||
this.signaturePreview = result.previewUrl;
|
||||
// In edit mode, delete old signature doc then upload new one
|
||||
if (this.clientId) {
|
||||
if (this.existingSignatureDocId) {
|
||||
this.clientsService.deleteClientDocument(this.clientId, this.existingSignatureDocId).subscribe({
|
||||
next: () => {
|
||||
this.clientsService.uploadClientSignatureImage(this.clientId, result.croppedFile).subscribe({
|
||||
next: (res: any) => {
|
||||
if (res?.resourceId) this.existingSignatureDocId = res.resourceId.toString();
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.clientsService.uploadClientSignatureImage(this.clientId, result.croppedFile).subscribe({
|
||||
next: (res: any) => {
|
||||
if (res?.resourceId) this.existingSignatureDocId = res.resourceId.toString();
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.clientsService.uploadClientSignatureImage(this.clientId, result.croppedFile).subscribe({
|
||||
next: (res: any) => {
|
||||
if (res?.resourceId) this.existingSignatureDocId = res.resourceId.toString();
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removePhoto() { this.photographFile = null; this.photographPreview = null; }
|
||||
removeSignature() { this.signatureFile = null; this.signaturePreview = null; }
|
||||
removePhoto() {
|
||||
this.photographFile = null;
|
||||
this.photographPreview = null;
|
||||
// In edit mode, delete from server
|
||||
if (this.clientId) {
|
||||
this.clientsService.deleteClientProfileImage(this.clientId).subscribe({ error: () => {} });
|
||||
}
|
||||
}
|
||||
|
||||
removeSignature() {
|
||||
this.signatureFile = null;
|
||||
this.signaturePreview = null;
|
||||
// In edit mode, delete from server
|
||||
if (this.clientId && this.existingSignatureDocId) {
|
||||
this.clientsService.deleteClientDocument(this.clientId, this.existingSignatureDocId).subscribe({
|
||||
next: () => {
|
||||
this.existingSignatureDocId = '';
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get kycData(): any {
|
||||
const formVal = this.readOnly ? this.kycForm.getRawValue() : this.kycForm.value;
|
||||
@@ -720,15 +1041,16 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
const aadhaarMasked = aadhaarRaw.length === 12 ? 'XXXXXXXX' + aadhaarRaw.slice(-4) : null;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const v = formVal;
|
||||
const formatDate = (d: any) => d instanceof Date ? this.dateUtils.formatDate(d, dateFormat) : (d || null);
|
||||
const formatDate = (d: any) => (d instanceof Date ? this.dateUtils.formatDate(d, dateFormat) : d || null);
|
||||
|
||||
return {
|
||||
locale: 'en',
|
||||
dateFormat,
|
||||
kycType: 1,
|
||||
riskCategory: v.riskCategory || 1,
|
||||
panNumber: v.hasPan ? (v.panNumber || null) : null,
|
||||
aadhaarNumberMasked: aadhaarMasked,
|
||||
// For non-individual: Company PAN is used as panNumber for verification
|
||||
panNumber: this.isNonIndividual ? v.enterprisePan || null : v.hasPan ? v.panNumber || null : null,
|
||||
aadhaarNumberMasked: this.isNonIndividual ? 'XXXXXXXX0000' : aadhaarMasked,
|
||||
panAadhaarSeedingStatus: this.panAadhaarSeedingStatus || null,
|
||||
gstinNumber: v.gstinNumber || null,
|
||||
enterprisePan: v.enterprisePan || null,
|
||||
@@ -761,7 +1083,15 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
get hasKycData(): boolean {
|
||||
const v = this.readOnly ? this.kycForm.getRawValue() : this.kycForm.value;
|
||||
return !!(v.panNumber || v.aadhaarNumber || v.gstinNumber || v.enterprisePan || v.cinNumber || v.kycDocumentName || v.form15Applicable);
|
||||
return !!(
|
||||
v.panNumber ||
|
||||
v.aadhaarNumber ||
|
||||
v.gstinNumber ||
|
||||
v.enterprisePan ||
|
||||
v.cinNumber ||
|
||||
v.kycDocumentName ||
|
||||
v.form15Applicable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,22 +1114,44 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.pan-err-dialog { max-width: 460px; }
|
||||
.pan-err-header {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 16px 24px 12px; border-bottom: 1px solid #fecaca;
|
||||
background: #fef2f2; font-size: 16px; font-weight: 600; color: #991b1b;
|
||||
}
|
||||
.pan-err-icon { color: #dc2626; }
|
||||
.pan-err-body { padding: 18px 24px !important; font-size: 14px; line-height: 1.6; color: #475569; }
|
||||
.pan-err-actions { padding: 10px 24px 16px !important; margin: 0 !important; }
|
||||
`],
|
||||
imports: [MatIcon, MatDialogContent, MatDialogActions, STANDALONE_SHARED_IMPORTS],
|
||||
styles: [
|
||||
`
|
||||
.pan-err-dialog {
|
||||
max-width: 460px;
|
||||
}
|
||||
.pan-err-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 24px 12px;
|
||||
border-bottom: 1px solid #fecaca;
|
||||
background: #fef2f2;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #991b1b;
|
||||
}
|
||||
.pan-err-icon {
|
||||
color: #dc2626;
|
||||
}
|
||||
.pan-err-body {
|
||||
padding: 18px 24px !important;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #475569;
|
||||
}
|
||||
.pan-err-actions {
|
||||
padding: 10px 24px 16px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
`
|
||||
],
|
||||
imports: [
|
||||
MatIcon,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
STANDALONE_SHARED_IMPORTS
|
||||
]
|
||||
})
|
||||
export class PanErrorDialogComponent {
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: { message: string }
|
||||
) {}
|
||||
data = inject<{ message: string }>(MAT_DIALOG_DATA);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user