From d72e969dc3946999ecd6c85ab473bcc252868543 Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 16 Apr 2026 13:51:11 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20Client=20&=20CKYC=20UI=20improvements?= =?UTF-8?q?=20=E2=80=94=20filters,=20KYC=20management,=20image=20crop+merg?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clients list: CKYC icon, CKYC Number column, status/legal form/CKYC filters - CKYC: SFTP upload logs, date formatting, modal z-index fix, non-dismissible dialogs - KYC Management: new standalone module with alert dashboard, notification scheduler, audit inventory, account status - Image cropper: ngx-image-cropper integration for front+back document merge into single PNG - Sidebar: KYC Management menu entry after CKYC Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 14 + package.json | 1 + src/app/accounting/accounting.component.scss | 8 +- src/app/app.module.ts | 2 + .../bank-calendar.component.scss | 4 +- .../cfr-lookup/cfr-lookup.component.scss | 11 + .../complaint-detail.component.scss | 2 +- .../complaint-list.component.scss | 2 + .../cfcfrms-dashboard.component.scss | 15 +- .../cfcfrms/escalate/escalate.component.scss | 2 +- .../fmr-submission.component.scss | 24 + .../report-fraud-txn.component.scss | 8 + .../report-mule/report-mule.component.scss | 9 + .../report-suspect.component.scss | 8 + .../ckyc-verification.component.html | 569 ++++- .../ckyc-verification.component.scss | 1167 ++++++++++ .../ckyc-verification.component.ts | 287 ++- src/app/ckyc/ckyc.component.html | 251 ++- src/app/ckyc/ckyc.component.scss | 462 ++++ src/app/ckyc/ckyc.component.ts | 18 +- .../record-master.component.html | 947 +++++++- .../record-master.component.scss | 1922 +++++++++++++++++ .../record-master/record-master.component.ts | 881 +++++++- .../client-address-step.component.scss | 12 +- .../client-entity-kyc-step.component.scss | 8 +- ...client-family-member-dialog.component.scss | 10 +- .../client-family-members-step.component.scss | 13 +- .../client-general-step.component.html | 12 + .../client-general-step.component.scss | 61 +- .../client-general-step.component.ts | 44 + .../din-verify-dialog.component.ts | 651 ++++++ .../client-india-address-step.component.scss | 4 +- .../client-kyc-step.component.html | 96 +- .../client-kyc-step.component.ts | 80 + .../image-crop-dialog.component.scss | 2 +- .../client-preview-step.component.scss | 42 +- .../address-tab/address-tab.component.scss | 8 +- .../ckyc-tab/ckyc-tab.component.html | 268 ++- .../ckyc-tab/ckyc-tab.component.scss | 420 +++- .../ckyc-tab/ckyc-tab.component.ts | 156 +- .../clients-view/clients-view.component.scss | 71 +- .../image-cropper-dialog.component.html | 28 + .../image-cropper-dialog.component.scss | 52 + .../image-cropper-dialog.component.ts | 93 + .../upload-document-dialog.component.html | 513 ++++- .../upload-document-dialog.component.scss | 691 +++++- .../upload-document-dialog.component.ts | 511 ++++- .../entity-classification-tab.component.scss | 3 +- .../family-members-tab.component.scss | 8 +- .../nominee-dialog.component.scss | 2 +- .../form60-tab/form60-tab.component.scss | 11 +- .../general-tab/general-tab.component.scss | 19 +- .../identities-tab.component.html | 27 +- .../identities-tab.component.scss | 252 ++- .../identities-tab.component.ts | 201 +- .../kyc-tab/kyc-tab.component.html | 109 +- .../kyc-tab/kyc-tab.component.scss | 276 ++- .../clients-view/kyc-tab/kyc-tab.component.ts | 235 ++ .../nominee-tab/nominee-tab.component.scss | 19 +- .../personal-data-tab.component.scss | 8 +- src/app/clients/clients.component.html | 81 +- src/app/clients/clients.component.ts | 51 +- src/app/clients/clients.service.ts | 97 + .../create-client.component.scss | 15 +- .../core/shell/sidenav/sidenav.component.html | 12 + .../core/shell/sidenav/sidenav.component.scss | 3 +- .../core/shell/toolbar/toolbar.component.scss | 7 +- .../crr-slr-dialog.component.scss | 1 + .../home/dashboard/dashboard.component.html | 41 +- .../home/dashboard/dashboard.component.scss | 117 +- src/app/home/home.component.scss | 42 +- .../kyc-management-routing.module.ts | 34 + .../kyc-management.component.html | 769 +++++++ .../kyc-management.component.scss | 1105 ++++++++++ .../kyc-management.component.ts | 485 +++++ .../kyc-management/kyc-management.module.ts | 22 + .../login-form/login-form.component.scss | 26 +- src/app/login/login.component.scss | 89 +- .../create-adhoc-query.component.scss | 3 +- .../bulk-import/bulk-import.component.scss | 28 +- .../bulk-loan-reassignmnet.component.scss | 10 +- .../manage-currencies.component.scss | 4 +- .../create-employee.component.scss | 2 +- .../edit-employee.component.scss | 2 +- .../view-employee.component.scss | 8 +- ...te-entity-data-table-checks.component.scss | 2 +- .../fund-mapping/fund-mapping.component.scss | 4 +- .../create-holiday.component.scss | 2 +- .../edit-holiday/edit-holiday.component.scss | 2 +- .../view-holidays.component.scss | 6 +- .../investors/investors.component.scss | 9 +- .../create-loan-originator.component.scss | 2 +- .../edit-loan-originator.component.scss | 2 +- .../view-loan-originator.component.scss | 2 +- ...-loan-provisioning-criteria.component.scss | 2 +- ...-loan-provisioning-criteria.component.scss | 2 +- .../create-fund/create-fund.component.scss | 2 +- .../edit-fund/edit-fund.component.scss | 2 +- .../view-fund/view-fund.component.scss | 2 +- .../create-office.component.scss | 2 +- .../edit-office/edit-office.component.scss | 2 +- .../general-tab/general-tab.component.scss | 4 +- .../view-office/view-office.component.scss | 2 +- .../organization/organization.component.scss | 28 +- .../create-payment-type.component.scss | 2 +- .../edit-payment-type.component.scss | 2 +- ...anding-instructions-history.component.scss | 10 +- .../allocate-cash.component.scss | 2 +- .../create-cashier.component.scss | 4 +- .../edit-cashier/edit-cashier.component.scss | 4 +- .../settle-cash/settle-cash.component.scss | 2 +- .../transactions/transactions.component.scss | 2 +- .../view-cashier/view-cashier.component.scss | 2 +- .../create-teller.component.scss | 2 +- .../edit-teller/edit-teller.component.scss | 2 +- .../view-teller/view-teller.component.scss | 2 +- .../create-charge.component.scss | 4 +- .../edit-charge/edit-charge.component.scss | 4 +- .../view-charge/view-charge.component.scss | 4 +- .../create-collateral.component.scss | 4 +- .../edit-collateral.component.scss | 4 +- .../view-collateral.component.scss | 2 +- .../create-current-product.component.scss | 8 +- ...reate-fixed-deposit-product.component.scss | 9 +- ...sit-product-accounting-step.component.scss | 4 +- ...eposit-product-charges-step.component.scss | 2 +- ...posit-product-currency-step.component.scss | 2 +- ...eposit-product-details-step.component.scss | 2 +- ...ct-interest-rate-chart-step.component.scss | 2 +- ...eposit-product-preview-step.component.scss | 2 +- ...posit-product-settings-step.component.scss | 2 +- ...-deposit-product-terms-step.component.scss | 2 +- .../create-floating-rate.component.scss | 2 +- .../edit-floating-rate.component.scss | 2 +- .../view-floating-rate.component.scss | 2 +- .../create-loan-product.component.scss | 8 +- ...oan-product-accounting-step.component.scss | 2 +- ...red-income-recognition-step.component.scss | 2 +- .../loan-product-charges-step.component.scss | 2 +- .../loan-product-currency-step.component.scss | 2 +- .../loan-product-details-step.component.scss | 2 +- ...roduct-interest-refund-step.component.scss | 2 +- .../loan-product-settings-step.component.scss | 4 +- .../loan-product-terms-step.component.scss | 8 +- .../create-bucket.component.scss | 2 +- .../edit-bucket/edit-bucket.component.scss | 2 +- .../create-range/create-range.component.scss | 4 +- .../edit-range/edit-range.component.scss | 4 +- .../manage-delinquency-buckets.component.scss | 2 +- .../create-tax-component.component.scss | 2 +- .../edit-tax-component.component.scss | 2 +- .../view-tax-component.component.scss | 6 +- .../manage-tax-configurations.component.scss | 4 +- .../create-tax-group.component.scss | 2 +- .../edit-tax-group.component.scss | 2 +- .../view-tax-group.component.scss | 6 +- .../create-product-mix.component.scss | 2 +- .../edit-product-mix.component.scss | 2 +- .../view-product-mix.component.scss | 2 +- src/app/products/products.component.scss | 28 +- ...e-recurring-deposit-product.component.scss | 9 +- ...sit-product-accounting-step.component.scss | 2 +- ...posit-product-currency-step.component.scss | 2 +- ...eposit-product-details-step.component.scss | 2 +- ...ct-interest-rate-chart-step.component.scss | 2 +- ...posit-product-settings-step.component.scss | 2 +- ...-deposit-product-terms-step.component.scss | 2 +- .../create-saving-product.component.scss | 8 +- .../edit-saving-product.component.scss | 8 +- ...ing-product-accounting-step.component.scss | 4 +- ...saving-product-charges-step.component.scss | 2 +- ...aving-product-currency-step.component.scss | 2 +- ...saving-product-details-step.component.scss | 2 +- ...aving-product-settings-step.component.scss | 2 +- .../saving-product-terms-step.component.scss | 2 +- .../create-share-product.component.scss | 8 +- ...are-product-accounting-step.component.scss | 2 +- .../share-product-charges-step.component.scss | 4 +- ...share-product-currency-step.component.scss | 3 +- .../share-product-details-step.component.scss | 3 +- .../share-product-preview-step.component.scss | 2 +- ...share-product-settings-step.component.scss | 2 +- .../share-product-terms-step.component.scss | 3 +- src/app/reports/reports.component.scss | 13 +- src/app/shared/footer/footer.component.scss | 109 +- .../form-dialog/form-dialog.component.scss | 2 +- src/app/shared/icons.module.ts | 34 +- src/app/shared/styles/list-layout.scss | 65 + .../audit-trails/audit-trails.component.scss | 4 +- .../create-code/create-code.component.scss | 2 +- .../codes/edit-code/edit-code.component.scss | 2 +- .../codes/view-code/view-code.component.scss | 3 +- .../add-role/add-role.component.scss | 2 +- .../edit-role/edit-role.component.scss | 2 +- .../view-role/view-role.component.scss | 2 +- src/app/system/system.component.scss | 26 +- .../checker-inbox.component.scss | 4 +- .../client-approval.component.scss | 4 +- .../loan-approval.component.scss | 4 +- .../loan-disbursal.component.scss | 4 +- .../reschedule-loan.component.scss | 4 +- .../checker-inbox-and-tasks.component.scss | 2 +- .../create-user/create-user.component.scss | 2 +- .../users/edit-user/edit-user.component.scss | 2 +- .../users/view-user/view-user.component.scss | 43 +- src/assets/images/cic-cibil.svg | 6 + src/assets/images/cic-crif.svg | 5 + src/assets/images/cic-equifax.svg | 5 + src/assets/images/cic-experian.svg | 5 + src/environments/.env.ts | 12 +- 210 files changed, 14273 insertions(+), 1139 deletions(-) create mode 100644 src/app/ckyc/ckyc-verification/ckyc-verification.component.scss create mode 100644 src/app/ckyc/record-master/record-master.component.scss create mode 100644 src/app/clients/client-stepper/client-general-step/din-verify-dialog.component.ts create mode 100644 src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.html create mode 100644 src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.scss create mode 100644 src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.ts create mode 100644 src/app/kyc-management/kyc-management-routing.module.ts create mode 100644 src/app/kyc-management/kyc-management.component.html create mode 100644 src/app/kyc-management/kyc-management.component.scss create mode 100644 src/app/kyc-management/kyc-management.component.ts create mode 100644 src/app/kyc-management/kyc-management.module.ts create mode 100644 src/assets/images/cic-cibil.svg create mode 100644 src/assets/images/cic-crif.svg create mode 100644 src/assets/images/cic-equifax.svg create mode 100644 src/assets/images/cic-experian.svg diff --git a/package-lock.json b/package-lock.json index 52ed90e45..db166ada6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "lightgallery": "^2.9.0", "lodash": "4.17.23", "moment": "^2.29.4", + "ngx-image-cropper": "^9.1.6", "ngx-mat-select-search": "^8.0.2", "rxjs": "^7.8.2", "signature_pad": "^5.1.3", @@ -18822,6 +18823,19 @@ "node": ">= 0.4.0" } }, + "node_modules/ngx-image-cropper": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/ngx-image-cropper/-/ngx-image-cropper-9.1.6.tgz", + "integrity": "sha512-b250YJ+jZovfqIj8vdEOrpEFay34be5f1Hpvg6Db68VMlvdyyuzboJdR0gCupbXtVcG6qQ86L7YG+SYxXJwApw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=17.3.0", + "@angular/core": ">=17.3.0" + } + }, "node_modules/ngx-mat-select-search": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-8.0.4.tgz", diff --git a/package.json b/package.json index 11771d707..156be4d23 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "lightgallery": "^2.9.0", "lodash": "4.17.23", "moment": "^2.29.4", + "ngx-image-cropper": "^9.1.6", "ngx-mat-select-search": "^8.0.2", "rxjs": "^7.8.2", "signature_pad": "^5.1.3", diff --git a/src/app/accounting/accounting.component.scss b/src/app/accounting/accounting.component.scss index 33dfdd24b..b86bfc60f 100644 --- a/src/app/accounting/accounting.component.scss +++ b/src/app/accounting/accounting.component.scss @@ -59,7 +59,7 @@ $accent: #0ea5e9; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { grid-template-columns: 1fr; } + @media (width <= 768px) { grid-template-columns: 1fr; } } .acc-card { @@ -68,14 +68,14 @@ $accent: #0ea5e9; gap: 14px; padding: 16px 18px; background: #fff; - border: 1px solid rgba(0, 0, 0, 0.06); + border: 1px solid rgb(0 0 0 / 6%); border-radius: 8px; text-decoration: none; cursor: pointer; transition: box-shadow 0.15s, transform 0.15s; &:hover { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 16px rgb(0 0 0 / 8%); transform: translateY(-1px); } @@ -125,7 +125,7 @@ $accent: #0ea5e9; :host-context(.dark-theme) { .acc-card { background: #1e1e2e; - border-color: rgba(255, 255, 255, 0.06); + border-color: rgb(255 255 255 / 6%); } .acc-card__title { color: #e0e0e0; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8d5313aeb..368a2b07e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -51,6 +51,7 @@ import { ConfigurationWizardModule } from './configuration-wizard/configuration- import { RemittancesModule } from './remittances/remittances.module'; import { SupersetAnalyticsModule } from './superset-analytics/superset-analytics.module'; import { CkycModule } from './ckyc/ckyc.module'; +import { KycManagementModule } from './kyc-management/kyc-management.module'; import { RbiCircularsModule } from './rbi-circulars/rbi-circulars.module'; import { CfcfrmsModule } from './cfcfrms/cfcfrms.module'; import { BankCalendarModule } from './bank-calendar/bank-calendar.module'; @@ -141,6 +142,7 @@ export function HttpLoaderFactory(http: HttpClient) { RemittancesModule, SupersetAnalyticsModule, CkycModule, + KycManagementModule, RbiCircularsModule, CfcfrmsModule, BankCalendarModule, diff --git a/src/app/bank-calendar/bank-calendar.component.scss b/src/app/bank-calendar/bank-calendar.component.scss index bc78d844e..62f55ee0f 100644 --- a/src/app/bank-calendar/bank-calendar.component.scss +++ b/src/app/bank-calendar/bank-calendar.component.scss @@ -434,7 +434,7 @@ $purple: #7c3aed; } // ─── Responsive ─── -@media screen and (max-width: 1200px) { +@media screen and (width <= 1200px) { .cal-months { grid-template-columns: repeat(2, 1fr); } @@ -444,7 +444,7 @@ $purple: #7c3aed; } } -@media screen and (max-width: 768px) { +@media screen and (width <= 768px) { .cal-months { grid-template-columns: 1fr; } diff --git a/src/app/cfcfrms/cfr-lookup/cfr-lookup.component.scss b/src/app/cfcfrms/cfr-lookup/cfr-lookup.component.scss index acc409812..06967caf9 100644 --- a/src/app/cfcfrms/cfr-lookup/cfr-lookup.component.scss +++ b/src/app/cfcfrms/cfr-lookup/cfr-lookup.component.scss @@ -14,11 +14,13 @@ .page-header { margin-bottom: 24px; + h2 { margin: 0; font-size: 24px; font-weight: 600; } + .subtitle { margin: 4px 0 0; color: #666; @@ -73,9 +75,11 @@ .result-card { border-left: 4px solid #ccc; } + .result-flagged { border-left-color: #ef4444; } + .result-clear { border-left-color: #10b981; } @@ -83,6 +87,7 @@ .warn-icon { color: #ef4444; } + .clear-icon { color: #10b981; } @@ -100,9 +105,11 @@ color: #666; font-weight: 500; } + .info-value { font-weight: 600; } + .ml-24 { margin-left: 24px; } @@ -110,9 +117,11 @@ .full-width { width: 100%; } + .mr-05 { margin-right: 0.5rem; } + .mono { font-family: 'Roboto Mono', monospace; } @@ -152,6 +161,7 @@ font-size: 13px; color: #7f1d1d; border-bottom: 1px solid #fee2e2; + &:last-child { border-bottom: none; } @@ -162,6 +172,7 @@ text-align: center; padding: 24px; color: #065f46; + p { margin-top: 12px; font-size: 15px; diff --git a/src/app/cfcfrms/complaint-detail/complaint-detail.component.scss b/src/app/cfcfrms/complaint-detail/complaint-detail.component.scss index 03b27c749..d6a73640b 100644 --- a/src/app/cfcfrms/complaint-detail/complaint-detail.component.scss +++ b/src/app/cfcfrms/complaint-detail/complaint-detail.component.scss @@ -54,7 +54,7 @@ gap: 24px; align-items: start; - @media (max-width: 1280px) { + @media (width <= 1280px) { grid-template-columns: 1fr; } } diff --git a/src/app/cfcfrms/complaint-list/complaint-list.component.scss b/src/app/cfcfrms/complaint-list/complaint-list.component.scss index 53b979c08..993b2b704 100644 --- a/src/app/cfcfrms/complaint-list/complaint-list.component.scss +++ b/src/app/cfcfrms/complaint-list/complaint-list.component.scss @@ -58,9 +58,11 @@ &.pending { background: #f59e0b; } + &.active { background: #1e88e5; } + &.resolved { background: #10b981; } diff --git a/src/app/cfcfrms/dashboard/cfcfrms-dashboard.component.scss b/src/app/cfcfrms/dashboard/cfcfrms-dashboard.component.scss index 2ffe944f4..061991332 100644 --- a/src/app/cfcfrms/dashboard/cfcfrms-dashboard.component.scss +++ b/src/app/cfcfrms/dashboard/cfcfrms-dashboard.component.scss @@ -35,7 +35,7 @@ gap: 20px; margin-bottom: 24px; - @media (max-width: 1280px) { + @media (width <= 1280px) { grid-template-columns: repeat(2, 1fr); } } @@ -80,36 +80,47 @@ &.stat-total { border-color: #1e88e5; + .stat-icon { background: #1e88e5; } + .stat-value { color: #1e88e5; } } + &.stat-pending { border-color: #f59e0b; + .stat-icon { background: #f59e0b; } + .stat-value { color: #f59e0b; } } + &.stat-lien { border-color: #eab308; + .stat-icon { background: #eab308; } + .stat-value { color: #92400e; } } + &.stat-recovered { border-color: #10b981; + .stat-icon { background: #10b981; } + .stat-value { color: #10b981; } @@ -122,7 +133,7 @@ gap: 20px; margin-bottom: 24px; - @media (max-width: 1280px) { + @media (width <= 1280px) { grid-template-columns: 1fr; } } diff --git a/src/app/cfcfrms/escalate/escalate.component.scss b/src/app/cfcfrms/escalate/escalate.component.scss index 1b93850dc..b449c40c4 100644 --- a/src/app/cfcfrms/escalate/escalate.component.scss +++ b/src/app/cfcfrms/escalate/escalate.component.scss @@ -35,7 +35,7 @@ gap: 24px; align-items: start; - @media (max-width: 1280px) { + @media (width <= 1280px) { grid-template-columns: 1fr; } } diff --git a/src/app/cfcfrms/fmr-submission/fmr-submission.component.scss b/src/app/cfcfrms/fmr-submission/fmr-submission.component.scss index a3aec417f..183f1ba73 100644 --- a/src/app/cfcfrms/fmr-submission/fmr-submission.component.scss +++ b/src/app/cfcfrms/fmr-submission/fmr-submission.component.scss @@ -11,70 +11,88 @@ max-width: 1200px; margin: 0 auto; } + .page-header { margin-bottom: 24px; + h2 { margin: 0; font-size: 24px; font-weight: 600; } + .subtitle { margin: 4px 0 0; color: #666; font-size: 14px; } } + .report-layout { display: flex; gap: 24px; } + .report-card { flex: 1; max-width: 700px; } + .wide-card { max-width: 900px; } + .full-width { width: 100%; } + .half-width { flex: 1; } + .form-row { display: flex; gap: 16px; } + .mr-05 { margin-right: 0.5rem; } + .mt-16 { margin-top: 16px; } + .submit-btn { margin-top: 8px; height: 48px; font-size: 16px; } + .inline-spinner { display: inline-block; margin-right: 8px; } + .checkbox-row { margin: 8px 0 16px; } + .success-result { text-align: center; padding: 32px 16px; + .success-icon { color: #10b981; margin-bottom: 16px; } + h3 { color: #10b981; margin-bottom: 24px; } } + .result-details { text-align: left; max-width: 500px; @@ -83,25 +101,31 @@ border-radius: 8px; padding: 16px; } + .result-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; + &:last-child { border-bottom: none; } } + .result-label { color: #666; font-weight: 500; } + .result-value { font-weight: 600; } + .mono { font-family: 'Roboto Mono', monospace; } + .status-submitted { color: #1e88e5; font-weight: 700; diff --git a/src/app/cfcfrms/report-fraud-txn/report-fraud-txn.component.scss b/src/app/cfcfrms/report-fraud-txn/report-fraud-txn.component.scss index 744868e97..3fae27dd4 100644 --- a/src/app/cfcfrms/report-fraud-txn/report-fraud-txn.component.scss +++ b/src/app/cfcfrms/report-fraud-txn/report-fraud-txn.component.scss @@ -14,11 +14,13 @@ .page-header { margin-bottom: 24px; + h2 { margin: 0; font-size: 24px; font-weight: 600; } + .subtitle { margin: 4px 0 0; color: #666; @@ -43,6 +45,7 @@ .mr-05 { margin-right: 0.5rem; } + .mt-16 { margin-top: 16px; } @@ -61,10 +64,12 @@ .success-result { text-align: center; padding: 32px 16px; + .success-icon { color: #10b981; margin-bottom: 16px; } + h3 { color: #10b981; margin-bottom: 24px; @@ -85,6 +90,7 @@ justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; + &:last-child { border-bottom: none; } @@ -94,9 +100,11 @@ color: #666; font-weight: 500; } + .result-value { font-weight: 600; } + .mono { font-family: 'Roboto Mono', monospace; } diff --git a/src/app/cfcfrms/report-mule/report-mule.component.scss b/src/app/cfcfrms/report-mule/report-mule.component.scss index 50c2bf4e1..32e7cac87 100644 --- a/src/app/cfcfrms/report-mule/report-mule.component.scss +++ b/src/app/cfcfrms/report-mule/report-mule.component.scss @@ -14,11 +14,13 @@ .page-header { margin-bottom: 24px; + h2 { margin: 0; font-size: 24px; font-weight: 600; } + .subtitle { margin: 4px 0 0; color: #666; @@ -43,6 +45,7 @@ .mr-05 { margin-right: 0.5rem; } + .mt-16 { margin-top: 16px; } @@ -61,10 +64,12 @@ .success-result { text-align: center; padding: 32px 16px; + .success-icon { color: #10b981; margin-bottom: 16px; } + h3 { color: #10b981; margin-bottom: 24px; @@ -85,6 +90,7 @@ justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; + &:last-child { border-bottom: none; } @@ -94,12 +100,15 @@ color: #666; font-weight: 500; } + .result-value { font-weight: 600; } + .mono { font-family: 'Roboto Mono', monospace; } + .status-flagged { color: #ef4444; font-weight: 700; diff --git a/src/app/cfcfrms/report-suspect/report-suspect.component.scss b/src/app/cfcfrms/report-suspect/report-suspect.component.scss index 3c3577da8..26c382c13 100644 --- a/src/app/cfcfrms/report-suspect/report-suspect.component.scss +++ b/src/app/cfcfrms/report-suspect/report-suspect.component.scss @@ -14,11 +14,13 @@ .page-header { margin-bottom: 24px; + h2 { margin: 0; font-size: 24px; font-weight: 600; } + .subtitle { margin: 4px 0 0; color: #666; @@ -43,6 +45,7 @@ .mr-05 { margin-right: 0.5rem; } + .mt-16 { margin-top: 16px; } @@ -61,10 +64,12 @@ .success-result { text-align: center; padding: 32px 16px; + .success-icon { color: #10b981; margin-bottom: 16px; } + h3 { color: #10b981; margin-bottom: 24px; @@ -85,6 +90,7 @@ justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; + &:last-child { border-bottom: none; } @@ -94,9 +100,11 @@ color: #666; font-weight: 500; } + .result-value { font-weight: 600; } + .mono { font-family: 'Roboto Mono', monospace; } diff --git a/src/app/ckyc/ckyc-verification/ckyc-verification.component.html b/src/app/ckyc/ckyc-verification/ckyc-verification.component.html index 604611c58..44d429fac 100644 --- a/src/app/ckyc/ckyc-verification/ckyc-verification.component.html +++ b/src/app/ckyc/ckyc-verification/ckyc-verification.component.html @@ -6,14 +6,565 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
- - -
- -

{{ 'labels.heading.CKYC' | translate }}

-

{{ 'labels.text.This section is under development.' | translate }}

+
+ +
+
+
+
+

CKYC Verification

+

Batch · Upload · Match — CERSAI compliance pipeline

- - +
+ +
+ + +
+
+
+
+
Total batches
+
{{ totalBatches }}
+
+
+
+
+
+
Total records
+
{{ totalRecords }}
+
+
+
+
+
+
Pending upload
+
{{ totalPending }}
+
+
+
+
+
+
Uploaded
+
{{ totalUploaded }}
+
+
+
+
+
+
Total size
+
{{ formatBytes(totalSize) }}
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ + +
+ + + @if (activeSection === 'zip') { +
+
+
+ +
+

ZIP Batch Details

+

CERSAI ZIP archives grouped by creation date

+
+
+ Phase 2 +
+ + @if (loading) { +
+ + Loading batches… +
+ } + + @if (error && !loading) { +
+ + {{ error }} +
+ } + + @if (!loading && !error && groups.length === 0) { +
+ +

No batches found

+

Create a batch from the Record Master to see it here.

+
+ } + + @if (!loading && groups.length > 0) { +
+ @for (group of groups; track group.dateLabel) { +
+ + + @if (group.expanded) { +
+ + + + + + + + + + + + + @for (batch of group.batches; track trackBatch($index, batch)) { + + + + + + + + + } + +
Batch IDFilenameRecordsSizeStatusActions
+ + {{ batch.batchId }} + + + {{ batch.filename }} + {{ batch.recordCount }}{{ formatBytes(batch.sizeBytes) }} + @if (batch.uploadedToCersai) { + + Uploaded + + } @else { + + Pending + + } + + + @if (batch.documentId) { + + } +
+
+ } +
+ } +
+ } +
+ + } + + + @if (activeSection === 'sftp') { +
+
+
+ +
+

SFTP Upload & ACK · API Match

+

CERSAI SFTP transfer history with ACK receipts and per-batch logs

+
+
+ Phase 3-4 +
+ +
+
+
+
+
{{ sftpSuccessCount }}
+
Uploaded successfully
+
+
+
+
+
+
{{ sftpFailedCount }}
+
Failed
+
+
+
+
+
+
{{ sftpPendingCount }}
+
Pending upload
+
+
+
+ + @if (loading) { +
+ + Loading logs… +
+ } + + @if (!loading && sftpLogs.length === 0) { +
+ +

No SFTP upload history yet

+

Upload a batch from the Record Master to see CERSAI transfer logs here.

+
+ } + + @if (!loading && sftpLogs.length > 0) { +
+ @for (log of sftpLogs; track log.id) { +
+
+ @if (isSftpSuccess(log)) { + + } @else if (isSftpFailed(log)) { + + } @else { + + } +
+
+
+ + {{ log.batchId }} + + @if (isSftpSuccess(log)) { + + Upload successful + + } @else if (isSftpFailed(log)) { + + Upload failed + + } @else { + + Pending + + } + + + {{ log.uploadedOnLabel || log.createdOnLabel || '—' }} + +
+ +
+
+ Filename + {{ log.filename }} +
+
+ Records + {{ log.recordCount }} +
+
+ Size + {{ formatBytes(log.sizeBytes) }} +
+ @if (log.cersaiReference) { +
+ CERSAI Ref + {{ log.cersaiReference }} +
+ } +
+ + @if (log.uploadRemarks) { +
+ + {{ log.uploadRemarks }} +
+ } +
+ +
+ + @if (log.documentId) { + + } +
+
+ } +
+ } +
+ } + + + @if (showManifest) { +
+
+
+
+ + Batch Manifest +
+ +
+ +
+ @if (manifestLoading) { +
+ + Loading manifest… +
+ } + + @if (manifestError) { +
+ + {{ manifestError }} +
+ } + + @if (manifestBatch && !manifestLoading) { +
+
+
+ Batch ID + {{ manifestBatch.batchId }} +
+
+ Created on + {{ manifestBatch.createdOnLabel || '—' }} +
+
+ Records + {{ manifestBatch.recordCount }} +
+
+ Size + {{ formatBytes(manifestBatch.sizeBytes) }} +
+
+ Status + @if (manifestBatch.uploadedToCersai) { + + Uploaded + + } @else { + + Pending + + } +
+
+ Filename + {{ manifestBatch.filename }} +
+
+ Checksum + {{ manifestBatch.checksum }} +
+
+
+ + @if (manifestBatch.manifest?.length > 0) { +
+ + Packaged clients ({{ manifestBatch.manifest.length }}) +
+
+ + + + + + + + + + + + + @for (entry of manifestBatch.manifest; track entry.clientId) { + + + + + + + + + } + +
ClientPSIDAdTotal size
+
+ {{ entry.displayName || 'Client ' + entry.clientId }} + @if (entry.cifNumber) { + + {{ entry.cifNumber }} + + } +
+
+ + + + + + + + + + + + + + + + + {{ formatBytes((entry.photo?.bytes || 0) + (entry.signature?.bytes || 0) + (entry.idProof?.bytes || 0) + (entry.addressProof?.bytes || 0)) }} +
+
+ } + } +
+ + +
+
+ }
diff --git a/src/app/ckyc/ckyc-verification/ckyc-verification.component.scss b/src/app/ckyc/ckyc-verification/ckyc-verification.component.scss new file mode 100644 index 000000000..50441b582 --- /dev/null +++ b/src/app/ckyc/ckyc-verification/ckyc-verification.component.scss @@ -0,0 +1,1167 @@ +/** + * 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/. + */ + +$primary: #1a73e8; +$primary-dark: #0c447c; +$primary-light: #e8f0fe; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; +$green: #1d9e75; +$green-light: #e1f5ee; +$teal-900: #085041; +$teal-700: #0f6e56; +$red: #d85a30; +$red-light: #faece7; +$amber: #ba7517; +$amber-light: #faeeda; +$purple: #6a4cb3; +$purple-light: #efeafb; + +.cv-container { + padding: 22px 26px 40px; + max-width: 1400px; + margin: 0 auto; +} + +/* ---------- Header ---------- */ +.cv-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 18px; + flex-wrap: wrap; + gap: 12px; + + &__left { + display: flex; + align-items: center; + gap: 14px; + } + + &__icon { + width: 44px; + height: 44px; + border-radius: 10px; + background: linear-gradient(135deg, $teal-700, $green); + color: $white; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + box-shadow: 0 3px 10px rgba($green, 0.3); + flex-shrink: 0; + } + + &__title { + margin: 0; + font-size: 20px; + font-weight: 700; + color: $gray-900; + } + + &__sub { + margin: 2px 0 0; + font-size: 13px; + color: $gray-500; + } +} + +.cv-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 0 16px; + height: 38px; + border-radius: 8px; + font-size: 13px; + font-weight: 600; + border: 1px solid $gray-200; + background: $white; + color: $gray-700; + cursor: pointer; + transition: all 0.15s; + + fa-icon { font-size: 12px; } + + &:hover:not(:disabled) { + background: $gray-50; + border-color: $gray-500; + } + + &--ghost { /* default */ } + + &--primary { + background: $primary; + border-color: $primary; + color: $white; + + &:hover:not(:disabled) { + background: #1557b5; + border-color: #1557b5; + } + } +} + +/* ---------- KPIs ---------- */ +.cv-kpis { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 14px; + margin-bottom: 18px; +} + +.cv-kpi { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + background: $white; + border: 1px solid $gray-200; + border-radius: 12px; + + &__icon { + width: 40px; + height: 40px; + border-radius: 10px; + color: $white; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + flex-shrink: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + + &--blue { background: linear-gradient(135deg, $primary, #4285f4); } + &--teal { background: linear-gradient(135deg, $teal-700, $green); } + &--amber { background: linear-gradient(135deg, $amber, #d4892f); } + &--green { background: linear-gradient(135deg, $green, #2fb88c); } + &--purple { background: linear-gradient(135deg, $purple, #8a6fd4); } + } + + &__label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.4px; + color: $gray-500; + margin-bottom: 2px; + } + + &__value { + font-size: 18px; + font-weight: 700; + color: $gray-900; + + &--mono { + font-family: 'SF Mono', Consolas, monospace; + font-size: 13px; + color: $primary; + } + } +} + +/* ---------- Filters ---------- */ +.cv-filters { + background: $white; + border: 1px solid $gray-200; + border-radius: 12px; + padding: 18px 20px; + margin-bottom: 18px; + + &__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px 18px; + margin-bottom: 16px; + } + + &__actions { + display: flex; + justify-content: flex-end; + gap: 10px; + padding-top: 14px; + border-top: 1px dashed $gray-200; + } +} + +.cv-field { + display: flex; + flex-direction: column; + gap: 5px; + min-width: 0; +} + +.cv-label { + font-size: 11px; + font-weight: 600; + color: $gray-700; + display: inline-flex; + align-items: center; + gap: 6px; + + fa-icon { color: $primary; font-size: 11px; } +} + +.cv-input { + width: 100%; + padding: 10px 12px; + border: 1px solid $gray-200; + border-radius: 8px; + background: $gray-50; + color: $gray-900; + font-size: 13px; + font-family: inherit; + height: 40px; + box-sizing: border-box; + transition: all 0.15s; + + &:focus { + outline: none; + border-color: $primary; + background: $white; + box-shadow: 0 0 0 3px rgba($primary, 0.1); + } +} + +select.cv-input { + appearance: none; + padding-right: 34px; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 12px center; + cursor: pointer; +} + +/* ---------- Section tabs ---------- */ +.cv-tabs { + display: flex !important; + gap: 10px !important; + margin-bottom: 16px !important; + background: $white !important; + border: 1px solid $gray-200 !important; + border-radius: 14px !important; + padding: 8px !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03) !important; +} + +button.cv-tab, +.cv-tabs .cv-tab { + flex: 1 1 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + gap: 10px !important; + padding: 14px 20px !important; + min-height: 52px !important; + border: 1.5px solid transparent !important; + border-radius: 10px !important; + background: transparent !important; + color: $gray-700 !important; + font-size: 14px !important; + font-weight: 600 !important; + cursor: pointer !important; + transition: all 0.15s !important; + font-family: inherit !important; + white-space: nowrap !important; + min-width: 0 !important; + line-height: 1 !important; + outline: none !important; + box-shadow: none !important; + + > fa-icon { + font-size: 15px !important; + color: $gray-500 !important; + } + + > span:not(.cv-tab__badge):not(.cv-tab__count) { + display: inline-flex !important; + align-items: center !important; + } + + &:hover { + background: $gray-50 !important; + color: $gray-900 !important; + + > fa-icon { color: $primary !important; } + } + + &--active { + background: $primary-light !important; + border-color: rgba($primary, 0.35) !important; + color: $primary-dark !important; + box-shadow: 0 2px 8px rgba($primary, 0.12) !important; + + > fa-icon { color: $primary !important; } + } +} + +.cv-tab__badge { + display: inline-block !important; + padding: 3px 9px !important; + border-radius: 10px !important; + font-size: 9px !important; + font-weight: 700 !important; + text-transform: uppercase !important; + letter-spacing: 0.3px !important; + margin-left: 2px !important; + + &--blue { + background: $primary-light !important; + color: $primary-dark !important; + } + + &--amber { + background: $amber-light !important; + color: $amber !important; + } +} + +.cv-tab__count { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 22px !important; + height: 20px !important; + padding: 0 8px !important; + border-radius: 10px !important; + background: $amber !important; + color: $white !important; + font-size: 10px !important; + font-weight: 700 !important; + font-variant-numeric: tabular-nums !important; +} + +@media (width <= 680px) { + .cv-tabs { flex-direction: column !important; } + button.cv-tab, + .cv-tabs .cv-tab { justify-content: space-between !important; } +} + +/* Hide the per-section header badge since the tab strip already carries it */ +.cv-section .cv-badge { display: none; } + +/* ---------- Section card ---------- */ +.cv-section { + background: $white; + border: 1px solid $gray-200; + border-radius: 14px; + padding: 18px 20px 20px; + margin-bottom: 18px; + + &__head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid $gray-100; + } + + &__title { + display: flex; + align-items: center; + gap: 12px; + + > fa-icon { + width: 36px; + height: 36px; + border-radius: 10px; + background: $primary-light; + color: $primary; + display: flex !important; + align-items: center; + justify-content: center; + font-size: 14px; + } + + h3 { + margin: 0; + font-size: 15px; + font-weight: 700; + color: $gray-900; + } + + p { + margin: 2px 0 0; + font-size: 11px; + color: $gray-500; + } + } + + &--placeholder { + background: linear-gradient(135deg, $gray-50, $primary-light); + } +} + +.cv-badge { + display: inline-block; + padding: 3px 10px; + border-radius: 10px; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.3px; + + &--blue { background: $primary-light; color: $primary-dark; } + &--amber { background: $amber-light; color: $amber; } +} + +/* ---------- State banners ---------- */ +.cv-state { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 30px 20px; + border-radius: 10px; + font-size: 13px; + + fa-icon { font-size: 18px; } + + &--loading { + background: $primary-light; + color: $primary; + border: 1px dashed rgba($primary, 0.3); + } + + &--error { + background: $red-light; + color: $red; + border: 1px solid rgba($red, 0.25); + } + + &--empty { + background: $gray-50; + color: $gray-500; + border: 1px dashed $gray-200; + + fa-icon { color: $primary; margin-bottom: 4px; } + h4 { margin: 0; font-size: 15px; font-weight: 600; color: $gray-900; } + p { margin: 0; font-size: 12px; } + } +} + +/* ---------- Batch groups ---------- */ +.cv-groups { + display: flex; + flex-direction: column; + gap: 10px; +} + +.cv-group { + background: $white; + border: 1px solid $gray-200; + border-radius: 10px; + overflow: hidden; + transition: border-color 0.15s, box-shadow 0.15s; + + &:hover { border-color: rgba($primary, 0.3); } + + &--open { + border-color: $primary-light; + box-shadow: 0 2px 10px rgba($primary, 0.05); + } + + &__head { + width: 100%; + padding: 14px 18px; + background: $gray-50; + border: none; + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + cursor: pointer; + font-family: inherit; + text-align: left; + + &:hover { background: $gray-100; } + } + + &__left { + display: flex; + align-items: center; + gap: 14px; + flex: 1; + min-width: 0; + } + + &__chev { + color: $primary; + font-size: 14px; + flex-shrink: 0; + } + + &__date-pill { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: $primary-light; + color: $primary-dark; + border-radius: 10px; + font-size: 12px; + font-weight: 700; + + fa-icon { color: $primary; font-size: 11px; } + } + + &__summary { + font-size: 12px; + color: $gray-700; + display: flex; + gap: 8px; + flex-wrap: wrap; + + strong { color: $gray-900; font-weight: 700; } + } + + &__sep { color: $gray-200; } + + &__right { + display: flex; + gap: 6px; + flex-shrink: 0; + } + + &__body { + background: $white; + border-top: 1px solid $gray-200; + overflow-x: auto; + } +} + +.cv-pill { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 700; + + fa-icon { font-size: 10px; } + + &--green { + background: $green-light; + color: $teal-900; + border: 1px solid rgba($green, 0.25); + } + + &--amber { + background: $amber-light; + color: $amber; + border: 1px solid rgba($amber, 0.3); + } + + &--red { + background: $red-light; + color: $red; + border: 1px solid rgba($red, 0.3); + } +} + +/* ---------- SFTP logs ---------- */ +.cv-sftp-kpis { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; + padding: 16px 20px 4px; +} + +.cv-sftp-kpi { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-radius: 12px; + border: 1px solid $gray-200; + background: $white; + + &__icon { + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; + } + + &__value { + font-size: 20px; + font-weight: 700; + color: $gray-900; + line-height: 1.1; + } + + &__label { + font-size: 11px; + color: $gray-500; + text-transform: uppercase; + letter-spacing: 0.4px; + margin-top: 2px; + } + + &--green { + border-color: rgba($green, 0.3); + background: linear-gradient(180deg, $green-light, $white); + .cv-sftp-kpi__icon { background: $green; color: $white; } + } + + &--red { + border-color: rgba($red, 0.3); + background: linear-gradient(180deg, $red-light, $white); + .cv-sftp-kpi__icon { background: $red; color: $white; } + } + + &--amber { + border-color: rgba($amber, 0.3); + background: linear-gradient(180deg, $amber-light, $white); + .cv-sftp-kpi__icon { background: $amber; color: $white; } + } +} + +.cv-sftp-logs { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px 20px 20px; +} + +.cv-sftp-log { + display: flex; + align-items: flex-start; + gap: 14px; + padding: 14px 16px; + border: 1px solid $gray-200; + border-left-width: 4px; + border-radius: 10px; + background: $white; + transition: background 0.15s, box-shadow 0.15s; + + &:hover { background: #fafbfc; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); } + + &--success { border-left-color: $green; } + &--failed { border-left-color: $red; } + + &__icon { + width: 36px; + height: 36px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + background: $gray-100; + color: $gray-500; + font-size: 16px; + flex-shrink: 0; + } + + &--success .cv-sftp-log__icon { background: $green-light; color: $green; } + &--failed .cv-sftp-log__icon { background: $red-light; color: $red; } + + &__body { flex: 1; min-width: 0; } + + &__head { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 8px; + } + + &__bid { + font-family: 'SF Mono', Consolas, monospace; + font-size: 13px; + font-weight: 700; + color: $primary; + + fa-icon { font-size: 11px; margin-right: 4px; } + } + + &__time { + font-size: 11px; + color: $gray-500; + margin-left: auto; + + fa-icon { font-size: 10px; margin-right: 4px; } + } + + &__meta { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 8px 16px; + font-size: 12px; + + > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; } + } + + &__label { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.4px; + color: $gray-500; + font-weight: 600; + } + + &__value { + color: $gray-900; + font-weight: 500; + word-break: break-all; + + &--mono { + font-family: 'SF Mono', Consolas, monospace; + font-size: 11px; + } + } + + &__remarks { + margin-top: 8px; + padding: 8px 10px; + border-radius: 8px; + background: $gray-50; + border: 1px solid $gray-200; + font-size: 12px; + color: $gray-700; + display: flex; + align-items: flex-start; + gap: 8px; + + fa-icon { font-size: 12px; color: $gray-500; margin-top: 2px; } + + &--err { + background: $red-light; + border-color: rgba($red, 0.3); + color: $red; + + fa-icon { color: $red; } + } + } + + &__actions { + display: flex; + align-items: center; + flex-shrink: 0; + } +} + +/* ---------- Batches table ---------- */ +.cv-batches { + width: 100%; + border-collapse: collapse; + font-size: 12px; + + thead tr { background: $gray-50; } + + th { + text-align: left; + padding: 10px 14px; + font-size: 10px; + font-weight: 700; + color: $gray-500; + text-transform: uppercase; + letter-spacing: 0.4px; + white-space: nowrap; + } + + td { + padding: 12px 14px; + border-top: 1px solid $gray-100; + color: $gray-900; + vertical-align: middle; + } + + tbody tr:hover { background: #f5f8fc; } + + &__num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; } + &__actions { text-align: right; width: 100px; } + + &__bid { + font-family: 'SF Mono', Consolas, monospace; + font-size: 12px; + font-weight: 600; + color: $primary; + white-space: nowrap; + + fa-icon { color: $primary; margin-right: 6px; font-size: 11px; } + } + + &__file { + color: $gray-700; + + fa-icon { color: $primary; margin-right: 6px; font-size: 11px; } + + span { + word-break: break-all; + max-width: 280px; + display: inline-block; + vertical-align: middle; + } + } +} + +.cv-action-btn { + width: 32px; + height: 32px; + border-radius: 8px; + border: 1px solid $gray-200; + background: $white; + color: $gray-700; + cursor: pointer; + margin-left: 4px; + transition: all 0.15s; + + fa-icon { font-size: 12px; } + + &:hover { + background: $primary-light; + color: $primary; + border-color: $primary; + } + + &--primary { + background: $primary; + color: $white; + border-color: $primary; + + &:hover { + background: #1557b5; + color: $white; + } + } +} + +/* ---------- SFTP placeholder ---------- */ +.cv-placeholder { + padding: 30px 24px; + text-align: center; + + &__icon { + width: 64px; + height: 64px; + border-radius: 16px; + background: $white; + border: 1px solid $primary-light; + color: $primary; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + margin: 0 auto 12px; + box-shadow: 0 4px 14px rgba($primary, 0.1); + } + + h4 { + margin: 0 0 6px; + font-size: 15px; + font-weight: 700; + color: $gray-900; + } + + p { + margin: 0 auto 14px; + font-size: 12px; + color: $gray-700; + max-width: 480px; + line-height: 1.55; + } + + &__tags { + display: flex; + justify-content: center; + gap: 8px; + flex-wrap: wrap; + } +} + +.cv-chip { + padding: 5px 12px; + background: $white; + border: 1px solid $gray-200; + border-radius: 16px; + font-size: 11px; + font-weight: 600; + color: $gray-700; +} + +/* ---------- Manifest modal ---------- */ +.cv-modal { + position: fixed; + inset: 0; + background: rgba(15, 25, 45, 0.55); + backdrop-filter: blur(3px); + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + + &__card { + background: $white; + border-radius: 16px; + width: 100%; + max-width: 720px; + max-height: 90vh; + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25); + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 22px; + border-bottom: 1px solid $gray-100; + } + + &__title { + display: flex; + align-items: center; + gap: 10px; + font-size: 16px; + font-weight: 700; + color: $gray-900; + + fa-icon { + width: 34px; + height: 34px; + border-radius: 10px; + background: linear-gradient(135deg, $primary, #4285f4); + color: $white; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + box-shadow: 0 3px 10px rgba($primary, 0.3); + } + } + + &__close { + width: 32px; + height: 32px; + border-radius: 8px; + border: 1px solid $gray-200; + background: $white; + color: $gray-500; + cursor: pointer; + + &:hover { + color: $red; + border-color: $red; + background: $red-light; + } + } + + &__body { + padding: 20px 22px; + overflow-y: auto; + flex: 1; + } + + &__footer { + padding: 16px 22px; + border-top: 1px solid $gray-100; + display: flex; + justify-content: flex-end; + gap: 10px; + background: $gray-50; + } +} + +.cv-manifest-head { + background: linear-gradient(135deg, $primary-light, $green-light); + border: 1px solid rgba($primary, 0.12); + border-radius: 10px; + padding: 16px 18px; + margin-bottom: 16px; + + &__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px 20px; + } + + &__grid-span { + grid-column: 1 / -1; + } + + /* Force stacked layout inside each field */ + > .cv-manifest-head__grid > div { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + + .cv-kpi__label { + display: block; + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: $teal-700; + opacity: 0.85; + margin: 0; + } + + .cv-kpi__value { + display: block; + font-size: 13px; + font-weight: 700; + color: $gray-900; + word-break: break-word; + line-height: 1.35; + + &--mono { + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; + font-size: 11px; + color: $primary-dark; + letter-spacing: 0.2px; + word-break: break-all; + } + } + + .cv-pill { + align-self: flex-start; + } + } +} + +.cv-manifest-title { + font-size: 11px; + font-weight: 700; + color: $gray-500; + text-transform: uppercase; + letter-spacing: 0.5px; + margin: 10px 0 8px; + display: inline-flex; + align-items: center; + gap: 6px; + + fa-icon { color: $primary; font-size: 12px; } +} + +.cv-manifest-table-wrap { + max-height: 320px; + overflow-y: auto; + border: 1px solid $gray-200; + border-radius: 10px; +} + +.cv-manifest-table { + width: 100%; + border-collapse: collapse; + font-size: 11px; + + thead tr { + background: $gray-50; + position: sticky; + top: 0; + z-index: 1; + } + + th { + text-align: left; + padding: 8px 12px; + font-size: 9px; + font-weight: 700; + color: $gray-500; + text-transform: uppercase; + letter-spacing: 0.4px; + white-space: nowrap; + border-bottom: 1px solid $gray-200; + } + + td { + padding: 8px 12px; + border-bottom: 1px solid $gray-100; + vertical-align: middle; + } + + tbody tr:last-child td { border-bottom: none; } + tbody tr:hover { background: #f5f8fc; } +} + +.cv-th-c { text-align: center !important; width: 38px; } +.cv-th-r { text-align: right !important; } +.cv-td-c { text-align: center; } +.cv-td-r { text-align: right; } + +.cv-manifest-client { + display: flex; + flex-direction: column; + gap: 2px; + + strong { + font-size: 12px; + font-weight: 600; + color: $gray-900; + } +} + +.cv-manifest-cif { + font-size: 10px; + color: $gray-500; + font-family: 'SF Mono', Consolas, monospace; + + fa-icon { font-size: 8px; margin-right: 2px; } +} + +.cv-dot { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 50%; + font-size: 9px; + font-weight: 700; + + &--ok { + background: $green-light; + color: $green; + border: 1px solid rgba($green, 0.3); + } + + &--miss { + background: $red-light; + color: $red; + border: 1px solid rgba($red, 0.3); + } +} + +.cv-manifest-size { + font-size: 11px; + font-weight: 600; + color: $gray-700; + font-family: 'SF Mono', Consolas, monospace; + white-space: nowrap; +} + +.cv-manifest-row--complete { + background: rgba($green, 0.03); +} + +/* ---------- Responsive ---------- */ +@media (width <= 1100px) { + .cv-kpis { grid-template-columns: repeat(3, 1fr); } +} + +@media (width <= 700px) { + .cv-kpis { grid-template-columns: repeat(2, 1fr); } + .cv-filters__grid { grid-template-columns: 1fr; } + .cv-manifest-head__grid { grid-template-columns: 1fr; } +} diff --git a/src/app/ckyc/ckyc-verification/ckyc-verification.component.ts b/src/app/ckyc/ckyc-verification/ckyc-verification.component.ts index 8d62b9d5c..e0ae34e81 100644 --- a/src/app/ckyc/ckyc-verification/ckyc-verification.component.ts +++ b/src/app/ckyc/ckyc-verification/ckyc-verification.component.ts @@ -6,16 +6,297 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component } from '@angular/core'; +import { Component, OnInit, DestroyRef, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormsModule } from '@angular/forms'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { ClientsService } from '../../clients/clients.service'; + +interface CkycBatch { + id: number; + batchId: string; + filename: string; + checksum: string; + recordCount: number; + sizeBytes: number; + documentId?: number; + createdOn: any; + uploadedToCersai: boolean; + uploadedOn?: any; + uploadStatus?: string; + uploadRemarks?: string; + cersaiReference?: string; + createdOnLabel?: string; + uploadedOnLabel?: string; +} + +interface CkycBatchGroup { + date: any; + dateLabel: string; + batchCount: number; + totalRecordCount: number; + totalSizeBytes: number; + uploadedCount: number; + pendingCount: number; + batches: CkycBatch[]; + expanded: boolean; +} @Component({ selector: 'mifosx-ckyc-verification', templateUrl: './ckyc-verification.component.html', + styleUrls: ['./ckyc-verification.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, - FaIconComponent + FaIconComponent, + FormsModule ] }) -export class CkycVerificationComponent {} +export class CkycVerificationComponent implements OnInit { + private clientsService = inject(ClientsService); + private destroyRef = inject(DestroyRef); + + filters: { fromDate: string; toDate: string; uploadedToCersai: '' | 'true' | 'false' } = { + fromDate: '', + toDate: '', + uploadedToCersai: '' + }; + + loading = false; + error = ''; + activeSection: 'zip' | 'sftp' = 'zip'; + groups: CkycBatchGroup[] = []; + totalBatches = 0; + totalRecords = 0; + totalSize = 0; + totalPending = 0; + totalUploaded = 0; + + /** SFTP log view */ + sftpLogs: CkycBatch[] = []; + sftpSuccessCount = 0; + sftpFailedCount = 0; + sftpPendingCount = 0; + + /** Manifest dialog */ + showManifest = false; + manifestLoading = false; + manifestError = ''; + manifestBatch: any = null; + + ngOnInit(): void { + this.fetch(); + } + + fetch(): void { + this.loading = true; + this.error = ''; + const query: any = { groupByDate: true }; + if (this.filters.fromDate) query.fromDate = this.filters.fromDate; + if (this.filters.toDate) query.toDate = this.filters.toDate; + if (this.filters.uploadedToCersai !== '') query.uploadedToCersai = this.filters.uploadedToCersai; + + this.clientsService + .getCkycBatches(query) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: (res: any) => { + const rawGroups = res?.groups || []; + this.groups = rawGroups.map((g: any, idx: number) => ({ + date: g.date, + dateLabel: this.formatJavaDate(g.date), + batchCount: g.batchCount || 0, + totalRecordCount: g.totalRecordCount || 0, + totalSizeBytes: g.totalSizeBytes || 0, + uploadedCount: g.uploadedCount || 0, + pendingCount: g.pendingCount || 0, + batches: (g.batches || []).map((b: any) => ({ + ...b, + createdOnLabel: this.formatJavaDate(b.createdOn), + uploadedOnLabel: this.formatJavaDateTime(b.uploadedOn) + })), + expanded: idx === 0 + })); + this.computeSummary(); + this.computeSftpLogs(); + this.loading = false; + }, + error: (err: any) => { + this.loading = false; + this.error = err?.error?.defaultUserMessage || err?.message || 'Failed to load CKYC batches.'; + this.groups = []; + } + }); + } + + private computeSummary(): void { + this.totalBatches = 0; + this.totalRecords = 0; + this.totalSize = 0; + this.totalPending = 0; + this.totalUploaded = 0; + for (const g of this.groups) { + this.totalBatches += g.batchCount; + this.totalRecords += g.totalRecordCount; + this.totalSize += g.totalSizeBytes; + this.totalPending += g.pendingCount; + this.totalUploaded += g.uploadedCount; + } + } + + resetFilters(): void { + this.filters = { fromDate: '', toDate: '', uploadedToCersai: '' }; + this.fetch(); + } + + toggleGroup(group: CkycBatchGroup): void { + group.expanded = !group.expanded; + } + + openManifest(batch: CkycBatch, event: Event): void { + event.stopPropagation(); + this.showManifest = true; + this.manifestLoading = true; + this.manifestError = ''; + this.manifestBatch = { ...batch, createdOnLabel: this.formatJavaDate(batch.createdOn) }; + + this.clientsService + .getCkycBatchById(batch.id) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: (res: any) => { + this.manifestBatch = { + ...res, + createdOnLabel: this.formatJavaDate(res.createdOn), + uploadedOnLabel: this.formatJavaDateTime(res.uploadedOn) + }; + this.manifestLoading = false; + }, + error: (err: any) => { + this.manifestLoading = false; + this.manifestError = err?.error?.defaultUserMessage || err?.message || 'Failed to load batch details.'; + } + }); + } + + closeManifest(): void { + this.showManifest = false; + this.manifestBatch = null; + this.manifestError = ''; + } + + downloadZip(batch: CkycBatch, event: Event): void { + event.stopPropagation(); + if (!batch.documentId) return; + this.clientsService + .downloadCkycBatchZip(batch.id, batch.documentId) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: (blob: Blob) => { + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = batch.filename || `batch-${batch.batchId}.zip`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, + error: () => undefined + }); + } + + formatJavaDate(value: any): string { + if (!value) return ''; + if (Array.isArray(value) && value.length >= 3) { + const [y, m, d] = value; + return `${String(d).padStart(2, '0')}-${String(m).padStart(2, '0')}-${y}`; + } + if (typeof value === 'string') return value; + return ''; + } + + formatJavaDateTime(value: any): string { + if (value == null || value === '') return ''; + const pad = (n: any) => String(n ?? 0).padStart(2, '0'); + if (Array.isArray(value) && value.length >= 3) { + const [y, mo, d, h = 0, mi = 0, s = 0] = value; + const date = `${pad(d)}-${pad(mo)}-${y}`; + if (value.length >= 4) { + return `${date} ${pad(h)}:${pad(mi)}:${pad(s)}`; + } + return date; + } + if (typeof value === 'string') { + const dt = new Date(value); + if (!isNaN(dt.getTime())) { + return `${pad(dt.getDate())}-${pad(dt.getMonth() + 1)}-${dt.getFullYear()} ${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`; + } + return value; + } + return ''; + } + + private computeSftpLogs(): void { + const flat: CkycBatch[] = []; + for (const g of this.groups) { + for (const b of g.batches) { + if (b.uploadedToCersai || b.uploadStatus || b.uploadedOn) { + flat.push(b); + } + } + } + flat.sort((a, b) => { + const at = this.dateValue(a.uploadedOn); + const bt = this.dateValue(b.uploadedOn); + return bt - at; + }); + this.sftpLogs = flat; + this.sftpSuccessCount = flat.filter((b) => this.isSftpSuccess(b)).length; + this.sftpFailedCount = flat.filter((b) => this.isSftpFailed(b)).length; + this.sftpPendingCount = this.totalPending; + } + + isSftpSuccess(b: CkycBatch): boolean { + if (b.uploadStatus) return b.uploadStatus.toLowerCase() === 'success'; + return !!b.uploadedToCersai; + } + + isSftpFailed(b: CkycBatch): boolean { + return !!b.uploadStatus && b.uploadStatus.toLowerCase() === 'failed'; + } + + private dateValue(value: any): number { + if (Array.isArray(value) && value.length >= 3) { + const [y, mo, d, h = 0, mi = 0, s = 0] = value; + return new Date(y, (mo || 1) - 1, d || 1, h, mi, s).getTime(); + } + if (typeof value === 'string') { + const t = Date.parse(value); + return isNaN(t) ? 0 : t; + } + return 0; + } + + formatBytes(bytes: number | undefined): string { + if (!bytes) return '—'; + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / 1024 / 1024).toFixed(2)} MB`; + } + + trackBatch(_: number, b: CkycBatch): number { + return b.id; + } + + /** Completion check per manifest entry */ + isEntryComplete(entry: any): boolean { + return ( + entry?.photo?.packaged && + entry?.signature?.packaged && + entry?.idProof?.packaged && + entry?.addressProof?.packaged + ); + } +} diff --git a/src/app/ckyc/ckyc.component.html b/src/app/ckyc/ckyc.component.html index 597ecb322..e90a7daec 100644 --- a/src/app/ckyc/ckyc.component.html +++ b/src/app/ckyc/ckyc.component.html @@ -6,120 +6,145 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
- -
-
- - - - - - - - - +
+ +
+
+
+
- -
- - - - - - - - - +
+

CKYC Compliance Centre

+

+ Central KYC registry management — collect, upload and verify client artefacts against CERSAI +

- +
+ + CERSAI ready +
+
+ + +
+
+ + 5-phase compliance pipeline + + CBS → CKYC automation flow +
+ +
+
+
1
+
+
Mark Non-CKYC
+
Identify missing CKYC registrations
+
+
+ +
+
2
+
+
Auto ZIP
+
Bundle into CERSAI format
+
+
+ +
+
3 · 4
+
+
SFTP Upload & API Match
+
Transfer · ACK · Resolve CKYC numbers
+
+
+ +
+
5
+
+
Status Update
+
Update master & MIS
+
+
+
+ + +

+ Quick actions +

+ +
diff --git a/src/app/ckyc/ckyc.component.scss b/src/app/ckyc/ckyc.component.scss index af9f4c075..3ade750b6 100644 --- a/src/app/ckyc/ckyc.component.scss +++ b/src/app/ckyc/ckyc.component.scss @@ -5,3 +5,465 @@ * 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/. */ + +$primary: #1a73e8; +$primary-dark: #0c447c; +$primary-light: #e8f0fe; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; +$teal: #0f6e56; +$teal-light: #e1f5ee; +$teal-bg: #f0fbf6; +$amber: #ba7517; +$amber-light: #faeeda; +$amber-bg: #fff8eb; +$purple: #6a4cb3; +$purple-light: #efeafb; +$purple-bg: #f8f5ff; + +.ckyc-dash { + padding: 24px 28px 40px; + max-width: 1400px; + margin: 0 auto; +} + +/* ---------- HERO ---------- */ +.ckyc-dash__hero { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 24px 28px; + background: linear-gradient(135deg, #0c447c 0%, #1a73e8 55%, #4285f4 100%); + border-radius: 16px; + color: $white; + margin-bottom: 22px; + position: relative; + overflow: hidden; + box-shadow: 0 6px 24px rgba(26, 115, 232, 0.2); + + &::before { + content: ''; + position: absolute; + top: -40%; + right: -10%; + width: 320px; + height: 320px; + border-radius: 50%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.15), transparent 70%); + pointer-events: none; + } + + &-left { + display: flex; + align-items: center; + gap: 18px; + min-width: 0; + position: relative; + } + + &-icon { + width: 58px; + height: 58px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.18); + border: 1px solid rgba(255, 255, 255, 0.3); + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + backdrop-filter: blur(10px); + flex-shrink: 0; + } + + &-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 30px; + font-size: 12px; + font-weight: 600; + backdrop-filter: blur(10px); + flex-shrink: 0; + position: relative; + + fa-icon { color: #9fe1cb; font-size: 14px; } + } +} + +.ckyc-dash__title { + margin: 0; + font-size: 22px; + font-weight: 700; + letter-spacing: -0.3px; +} + +.ckyc-dash__sub { + margin: 4px 0 0; + font-size: 13px; + opacity: 0.85; + max-width: 560px; + line-height: 1.5; +} + +/* ---------- Pipeline banner ---------- */ +.ckyc-pipeline { + background: $white; + border: 1px solid $gray-200; + border-radius: 14px; + padding: 18px 20px; + margin-bottom: 22px; + + &__head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px dashed $gray-200; + } + + &__heading { + font-size: 13px; + font-weight: 700; + color: $gray-900; + display: inline-flex; + align-items: center; + gap: 8px; + + fa-icon { color: $primary; } + } + + &__hint { + font-size: 11px; + color: $gray-500; + font-weight: 500; + } + + &__track { + display: flex; + align-items: stretch; + gap: 4px; + overflow-x: auto; + padding: 4px 2px; + + &::-webkit-scrollbar { height: 4px; } + &::-webkit-scrollbar-thumb { background: $gray-200; border-radius: 4px; } + } +} + +.ckyc-phase { + flex: 1; + min-width: 140px; + background: $primary-light; + border: 1px solid rgba($primary, 0.15); + border-radius: 10px; + padding: 12px 12px 14px; + text-align: center; + position: relative; + transition: transform 0.15s; + + &:hover { transform: translateY(-2px); } + + &__num { + position: absolute; + top: 8px; + right: 10px; + width: 20px; + height: 20px; + border-radius: 50%; + background: $primary; + color: $white; + font-size: 10px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + + &--dual { + width: auto; + min-width: 20px; + height: 20px; + padding: 0 8px; + border-radius: 10px; + font-size: 10px; + letter-spacing: 0.5px; + white-space: nowrap; + } + } + + &__icon { + width: 38px; + height: 38px; + border-radius: 10px; + background: $primary; + color: $white; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + margin: 0 auto 8px; + box-shadow: 0 3px 10px rgba($primary, 0.25); + } + + &__title { + font-size: 12px; + font-weight: 700; + color: $primary-dark; + margin-bottom: 3px; + } + + &__desc { + font-size: 10px; + color: $gray-700; + line-height: 1.35; + } + + &__arrow { + display: flex; + align-items: center; + color: $gray-200; + font-size: 16px; + padding: 0 2px; + flex-shrink: 0; + } + + &--accent { + background: $amber-light; + border-color: rgba($amber, 0.25); + + .ckyc-phase__icon { + background: $amber; + box-shadow: 0 3px 10px rgba($amber, 0.3); + } + + .ckyc-phase__num { + background: $amber; + } + + .ckyc-phase__title { color: #633806; } + } + + &--wide { + flex: 1.8; + min-width: 210px; + } +} + +/* ---------- Section title ---------- */ +.ckyc-dash__section-title { + font-size: 14px; + font-weight: 700; + color: $gray-900; + margin: 10px 0 14px; + display: inline-flex; + align-items: center; + gap: 8px; + text-transform: uppercase; + letter-spacing: 0.4px; + + fa-icon { color: $primary; font-size: 14px; } +} + +/* ---------- Menu grid ---------- */ +.ckyc-dash__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 18px; +} + +.ckyc-card { + display: flex; + flex-direction: column; + padding: 22px 22px 18px; + background: $white; + border: 1px solid $gray-200; + border-radius: 16px; + text-decoration: none; + color: $gray-900; + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; + cursor: pointer; + position: relative; + overflow: hidden; + min-height: 220px; + + &__glow { + position: absolute; + top: -50px; + right: -50px; + width: 160px; + height: 160px; + border-radius: 50%; + opacity: 0.12; + pointer-events: none; + transition: transform 0.4s, opacity 0.3s; + } + + &:hover { + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); + + .ckyc-card__glow { + transform: scale(1.25); + opacity: 0.2; + } + + .ckyc-card__cta { + background: $primary; + color: $white; + + fa-icon { transform: translateX(3px); } + } + } + + &__icon { + width: 52px; + height: 52px; + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: $white; + margin-bottom: 16px; + position: relative; + z-index: 1; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1); + } + + &__body { + flex: 1; + position: relative; + z-index: 1; + } + + &__title { + font-size: 16px; + font-weight: 700; + color: $gray-900; + margin-bottom: 6px; + letter-spacing: -0.2px; + } + + &__desc { + font-size: 12px; + color: $gray-700; + line-height: 1.55; + margin: 0 0 12px; + } + + &__tags { + display: flex; + gap: 6px; + flex-wrap: wrap; + } + + &__cta { + display: inline-flex; + align-items: center; + gap: 8px; + align-self: flex-start; + padding: 8px 14px; + border-radius: 10px; + background: $gray-50; + color: $gray-700; + font-size: 12px; + font-weight: 600; + margin-top: 14px; + border: 1px solid $gray-200; + transition: all 0.2s; + position: relative; + z-index: 1; + + fa-icon { + font-size: 11px; + transition: transform 0.2s; + } + } + + /* ------ Colour variants ------ */ + &--blue { + .ckyc-card__icon { + background: linear-gradient(135deg, $primary, #4285f4); + } + .ckyc-card__glow { + background: radial-gradient(circle, $primary, transparent 60%); + } + &:hover .ckyc-card__cta { + background: $primary; + border-color: $primary; + } + } + + &--teal { + .ckyc-card__icon { + background: linear-gradient(135deg, $teal, #1d9e75); + } + .ckyc-card__glow { + background: radial-gradient(circle, $teal, transparent 60%); + } + &:hover .ckyc-card__cta { + background: $teal; + border-color: $teal; + } + } + + &--amber { + .ckyc-card__icon { + background: linear-gradient(135deg, $amber, #d4892f); + } + .ckyc-card__glow { + background: radial-gradient(circle, $amber, transparent 60%); + } + &:hover .ckyc-card__cta { + background: $amber; + border-color: $amber; + } + } + + &--purple { + .ckyc-card__icon { + background: linear-gradient(135deg, $purple, #8a6fd4); + } + .ckyc-card__glow { + background: radial-gradient(circle, $purple, transparent 60%); + } + &:hover .ckyc-card__cta { + background: $purple; + border-color: $purple; + } + } + +} + +.ckyc-tag { + display: inline-block; + padding: 3px 9px; + background: $gray-100; + color: $gray-700; + font-size: 10px; + font-weight: 700; + border-radius: 10px; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +/* ---------- Responsive ---------- */ +@media (width <= 900px) { + .ckyc-dash { padding: 16px 18px 30px; } + .ckyc-dash__hero { flex-direction: column; align-items: flex-start; } + .ckyc-pipeline__track { flex-direction: row; flex-wrap: nowrap; } +} + +@media (width <= 600px) { + .ckyc-dash__grid { grid-template-columns: 1fr; } + .ckyc-phase { min-width: 120px; } +} diff --git a/src/app/ckyc/ckyc.component.ts b/src/app/ckyc/ckyc.component.ts index 315569c26..521a90b25 100644 --- a/src/app/ckyc/ckyc.component.ts +++ b/src/app/ckyc/ckyc.component.ts @@ -7,11 +7,7 @@ */ import { Component } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { MatNavList, MatListItem } from '@angular/material/list'; -import { MatIcon } from '@angular/material/icon'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { MatLine } from '@angular/material/grid-list'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -20,17 +16,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; styleUrls: ['./ckyc.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, - MatNavList, - MatListItem, - MatIcon, - FaIconComponent, - MatLine + FaIconComponent ] }) -export class CkycComponent { - arrowBooleans: boolean[] = new Array(4).fill(false); - - arrowBooleansToggle(arrowNumber: number) { - this.arrowBooleans[arrowNumber] = !this.arrowBooleans[arrowNumber]; - } -} +export class CkycComponent {} diff --git a/src/app/ckyc/record-master/record-master.component.html b/src/app/ckyc/record-master/record-master.component.html index c7ad688fc..c37e72f77 100644 --- a/src/app/ckyc/record-master/record-master.component.html +++ b/src/app/ckyc/record-master/record-master.component.html @@ -6,14 +6,943 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
- - -
- -

{{ 'labels.heading.Record Master' | translate }}

-

{{ 'labels.text.This section is under development.' | translate }}

+
+ +
+
+
+
- - +
+

{{ 'labels.heading.Record Master' | translate }}

+

Clients missing CKYC registration or required artefacts

+
+
+
+ +
+
+ + +
+ + + +
+ + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ + + @if (loading) { +
+ + Loading CKYC records… +
+ } + + @if (error && !loading) { +
+ + {{ error }} +
+ } + + @if (!loading && !error && rows.length === 0) { +
+ +

No records found

+

All clients matching your filters have complete CKYC artefacts. 🎉

+
+ } + + + @if (!loading && rows.length > 0) { +
+
+ + + Showing {{ pageStart }}–{{ pageEnd }} of + {{ totalFilteredRecords }} + + @if (selected.size > 0) { + + + {{ selected.size }} selected + + } +
+ +
+ + + + + + + + + + + + + + @for (row of rows; track row.clientId) { + + + + + + + + + + } + +
+ + ClientContactAddressArtefactsCompletionActions
+ + +
+
{{ row.fullName?.charAt(0) || '?' }}
+
+
{{ row.fullName }}
+ +
+
+
+
+ + {{ row.email || 'No email' }} +
+
+ + {{ row.mobile || 'No mobile' }} +
+
+ + {{ row.address || 'No address on file' }} + +
+ + P + + + S + + + ID + + + Ad + +
+
+
+ {{ artefactCompletion(row) }}/4 +
+
+ + +
+
+ + +
+
+ Rows per page: + +
+
+ + {{ pageStart }}–{{ pageEnd }} + +
+
+
+ } + + + @if (selected.size > 0) { +
+
+
+ + {{ selected.size }} selected +
+ @if (selectedIncompleteCount > 0) { + + + {{ selectedIncompleteCount }} incomplete + + } +
+
+ + +
+
+ } + + + @if (showViewDialog && viewDetails) { +
+
+
+
+ + Client KYC details +
+ +
+ +
+ +
+
+ {{ viewDetails.row.fullName?.charAt(0) || '?' }} +
+
+
{{ viewDetails.row.fullName }}
+
+ + CIF {{ viewDetails.row.accountNo }} + + • + + + {{ viewDetails.row.email || 'No email' }} + + • + + + {{ viewDetails.row.mobile || 'No mobile' }} + +
+
+
+ + @if (viewLoading) { +
+ + Loading KYC details… +
+ } + + +
+ KYC Snapshot +
+
+
+
KYC Status
+ @if (viewDetails.kycStatus) { +
+ {{ viewDetails.kycStatus }} +
+ } @else { +
—
+ } +
+ +
+
KYC Date
+
+ {{ viewDetails.kycDate || '—' }} +
+
+ +
+
Expiry Date
+
+ {{ viewDetails.expiryDate || '—' }} +
+
+ +
+
Risk Category
+
+ {{ viewDetails.riskCategory || '—' }} +
+
+
+ + +
+ CKYC Documents +
+
+ + + + +
+ + +
+
+
+
+
{{ label }}
+
+ {{ slot.name }} + @if (slot.key) { + {{ slot.key }} + } +
+
+ +
+ + @if (slot.files?.length) { +
+ @for (file of slot.files; track file.id) { +
+ @if (file.previewUrl && file.isImage !== false && !file.isPdf) { + + } @else if (file.isPdf) { +
+ +
+ } @else { +
+ +
+ } +
+ @if (file.fileName) { + {{ file.fileName }} + } @else { + {{ file.name }} + } + +
+
+ } +
+ } @else if (slot.present) { +
+ + Loading preview… +
+ } +
+
+
+ + +
+
+ } + + + @if (showZipDialog) { +
+
+
+
+ + Auto ZIP Creation +
+ +
+ +
+

+ Bundling {{ selected.size }} client record{{ selected.size === 1 ? '' : 's' }} + into CERSAI-format ZIP. Files will be packaged with an SHA-256 checksum and registered for SFTP upload. +

+ + @if (selectedIncompleteCount > 0 && !zipRunning && !zipResult) { +
+ +
+ {{ selectedIncompleteCount }} record(s) are missing artefacts. + They will still be packaged but flagged as incomplete in the manifest. +
+
+ } + + @if (!zipRunning && !zipResult) { +
+ + +
+ } + + +
    + @for (phase of zipPhases; track phase.key; let i = $index) { +
  1. +
    + @if (phase.status === 'done') { + + } @else if (phase.status === 'active') { + + } @else if (phase.status === 'failed') { + + } @else { + {{ i + 1 }} + } +
    +
    +
    + {{ phase.label }} +
    + @if (phase.message) { +
    {{ phase.message }}
    + } +
    +
  2. + } +
+ + @if (zipResult && !zipResult.error) { +
+
+ + Batch created successfully +
+
+
+ Batch ID + {{ zipResult.batchId }} +
+
+ Filename + {{ zipResult.filename }} +
+
+ Records + {{ zipResult.recordCount }} +
+ @if (zipResult.sizeBytes) { +
+ Size + {{ (zipResult.sizeBytes / 1024 / 1024).toFixed(2) }} MB +
+ } + @if (zipResult.createdOn) { +
+ Created on + {{ zipResult.createdOn }} +
+ } + @if (zipResult.documentId) { +
+ Document ID + #{{ zipResult.documentId }} +
+ } +
+ Checksum + {{ zipResult.checksum }} +
+
+
+ + @if (zipResult.manifest && zipResult.manifest.length > 0) { +
+ + + @if (manifestExpanded) { +
+ + + + + + + + + + + + + @for (entry of zipResult.manifest; track entry.clientId) { + + + + + + + + + } + +
ClientPSIDAdTotal size
+
+ {{ entry.displayName || 'Client ' + entry.clientId }} + @if (entry.cifNumber) { + + {{ entry.cifNumber }} + + } +
+
+ + + + + + + + + + + + + + + + + {{ formatBytes((entry.photo?.bytes || 0) + (entry.signature?.bytes || 0) + (entry.idProof?.bytes || 0) + (entry.addressProof?.bytes || 0)) }} +
+
+ } +
+ } + } + + @if (zipResult?.error) { +
+ +
+ Batch creation failed + {{ zipResult!.error }} +
+
+ } +
+ + +
+
+ } + + + @if (showUploadDialog) { +
+
+
+
+ + SFTP Upload to CERSAI +
+ +
+ +
+

+ Transferring batch to the CERSAI SFTP drop server and waiting for + acknowledgement. On success, the returned CKYC numbers will be saved + against each client's master record. +

+ + @if (uploadResult?.status === 'ALREADY_UPLOADED') { +
+ +
+ Batch already uploaded. + {{ uploadResult?.remarks || 'No action was taken.' }} +
+
+ } + + +
    + @for (phase of uploadPhases; track phase.key; let i = $index) { +
  1. +
    + @if (phase.status === 'done') { + + } @else if (phase.status === 'active') { + + } @else if (phase.status === 'failed') { + + } @else { + {{ i + 1 }} + } +
    +
    +
    + {{ phase.label }} +
    + @if (phase.message) { +
    {{ phase.message }}
    + } +
    +
  2. + } +
+ + @if (uploadResult && !uploadResult.error) { +
+
+ + + @if (uploadResult.status === 'ALREADY_UPLOADED') { + Batch already uploaded to CERSAI + } @else { + Upload successful — ACK received + } + +
+
+
+ CERSAI Reference + {{ uploadResult.cersaiReference }} +
+
+ Batch ID + {{ uploadResult.batchId }} +
+
+ Uploaded on + {{ uploadResult.uploadedOn }} +
+ @if (uploadResult.receivedAt) { +
+ Received at + {{ uploadResult.receivedAt }} +
+ } +
+ Records + + {{ uploadResult.processedRecords }} / {{ uploadResult.totalRecords }} + +
+
+ Status + + + {{ uploadResult.status }} + + +
+
+ Checksum sent + {{ uploadResult.checksumSent }} +
+
+ Checksum verified + {{ uploadResult.checksumVerified }} +
+
+
+ + @if (uploadResult.perClientCkycNumbers && uploadResult.perClientCkycNumbers.length > 0) { +
+
+ + Issued CKYC numbers ({{ uploadResult.perClientCkycNumbers.length }}) +
+
+ + + + + + + + + + + @for (c of uploadResult.perClientCkycNumbers; track c.clientId) { + + + + + + + } + +
ClientCIFCKYC NumberStatus
#{{ c.clientId }}{{ c.cifNumber }}{{ c.ckycNumber }} + + {{ c.status }} + +
+
+
+ } + } + + @if (uploadResult?.error) { +
+ +
+ Upload failed + {{ uploadResult!.error }} +
+
+ } +
+ + +
+
+ }
diff --git a/src/app/ckyc/record-master/record-master.component.scss b/src/app/ckyc/record-master/record-master.component.scss new file mode 100644 index 000000000..8eb33fc24 --- /dev/null +++ b/src/app/ckyc/record-master/record-master.component.scss @@ -0,0 +1,1922 @@ +/** + * 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/. + */ + +$primary: #1a73e8; +$primary-dark: #0c447c; +$primary-light: #e8f0fe; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; +$green: #1d9e75; +$green-light: #e1f5ee; +$red: #d85a30; +$red-light: #faece7; +$amber: #ba7517; +$amber-light: #faeeda; +$teal-900: #085041; + +.rm-container { padding: 20px 24px 40px; } + +.rm-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 18px; + flex-wrap: wrap; + + &__left { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; + } + + &__icon { + width: 44px; + height: 44px; + border-radius: 10px; + background: linear-gradient(135deg, $primary, #4285f4); + color: $white; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + box-shadow: 0 3px 10px rgba(26, 115, 232, 0.25); + flex-shrink: 0; + } + + &__title { + margin: 0; + font-size: 20px; + font-weight: 700; + color: $gray-900; + } + + &__sub { + margin: 2px 0 0; + font-size: 13px; + color: $gray-500; + } + + &__actions { + display: flex; + gap: 10px; + } +} + +.rm-btn { + border-radius: 8px !important; + font-size: 13px !important; + font-weight: 600 !important; + height: 40px !important; + padding: 0 16px !important; + display: inline-flex !important; + align-items: center; + gap: 8px; + + fa-icon { font-size: 13px; } + + &--ghost { color: $gray-700 !important; border-color: $gray-200 !important; } + + &--primary { + box-shadow: 0 1px 3px rgba($primary, 0.3) !important; + } +} + +/* 3-tab filter — elevated specificity + !important to beat Material button resets */ +.rm-tabs { + display: flex !important; + gap: 10px !important; + margin-bottom: 16px !important; + background: $white !important; + border: 1px solid $gray-200 !important; + border-radius: 14px !important; + padding: 8px !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03) !important; +} + +button.rm-tab, +.rm-tabs .rm-tab { + flex: 1 1 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + gap: 10px !important; + padding: 12px 20px !important; + min-height: 48px !important; + height: auto !important; + border: 1.5px solid transparent !important; + border-radius: 10px !important; + background: transparent !important; + color: $gray-700 !important; + font-size: 13px !important; + font-weight: 600 !important; + cursor: pointer !important; + transition: all 0.15s !important; + font-family: inherit !important; + white-space: nowrap !important; + min-width: 0 !important; + line-height: 1 !important; + letter-spacing: 0.1px !important; + outline: none !important; + box-shadow: none !important; + + fa-icon { + font-size: 14px !important; + color: $gray-500 !important; + display: inline-flex !important; + align-items: center !important; + } + + > span { + display: inline-flex !important; + align-items: center !important; + } + + &:hover { + background: $gray-50 !important; + color: $gray-900 !important; + + fa-icon { color: $primary !important; } + } +} + +.rm-tab__count { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 24px !important; + height: 22px !important; + padding: 0 9px !important; + border-radius: 11px !important; + background: $gray-100 !important; + color: $gray-700 !important; + font-size: 11px !important; + font-weight: 700 !important; + font-variant-numeric: tabular-nums !important; + margin-left: 2px !important; +} + +/* Active state — blue / amber / green variants */ +button.rm-tab.rm-tab--active, +.rm-tabs .rm-tab.rm-tab--active { + background: $primary-light !important; + border-color: rgba($primary, 0.35) !important; + color: $primary-dark !important; + box-shadow: 0 2px 8px rgba($primary, 0.12) !important; + + fa-icon { color: $primary !important; } + + .rm-tab__count { + background: $primary !important; + color: $white !important; + } +} + +button.rm-tab--amber.rm-tab--active, +.rm-tabs .rm-tab--amber.rm-tab--active { + background: $amber-light !important; + border-color: rgba($amber, 0.4) !important; + color: #633806 !important; + box-shadow: 0 2px 8px rgba($amber, 0.18) !important; + + fa-icon { color: $amber !important; } + + .rm-tab__count { + background: $amber !important; + color: $white !important; + } +} + +button.rm-tab--green.rm-tab--active, +.rm-tabs .rm-tab--green.rm-tab--active { + background: $green-light !important; + border-color: rgba($green, 0.4) !important; + color: $teal-900 !important; + box-shadow: 0 2px 8px rgba($green, 0.18) !important; + + fa-icon { color: $green !important; } + + .rm-tab__count { + background: $green !important; + color: $white !important; + } +} + +@media (width <= 680px) { + .rm-tabs { flex-direction: column; } + .rm-tab { justify-content: space-between; } +} + +/* Filters */ +.rm-filters { + background: $white; + border: 1px solid $gray-200; + border-radius: 12px; + padding: 18px 20px; + margin-bottom: 18px; + + &__grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 14px 18px; + margin-bottom: 16px; + } + + &__actions { + display: flex; + justify-content: flex-end; + gap: 10px; + padding-top: 14px; + border-top: 1px dashed $gray-200; + } +} + +@media (width <= 1100px) { + .rm-filters__grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (width <= 720px) { + .rm-filters__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (width <= 480px) { + .rm-filters__grid { + grid-template-columns: 1fr; + } +} + +.rm-field { + display: flex; + flex-direction: column; + gap: 5px; + min-width: 0; + + &--remarks { + margin: 4px 0 14px; + } +} + +.rm-textarea { + resize: vertical; + min-height: 50px; + height: auto; + font-family: inherit; + line-height: 1.5; +} + +.rm-label__opt { + font-weight: 500; + color: $gray-500; + font-size: 10px; + margin-left: 2px; +} + +.rm-label { + font-size: 11px; + font-weight: 600; + color: $gray-700; + display: inline-flex; + align-items: center; + gap: 6px; + + fa-icon { color: $primary; font-size: 11px; } +} + +.rm-input { + width: 100%; + padding: 10px 12px; + border: 1px solid $gray-200; + border-radius: 8px; + background: $gray-50; + color: $gray-900; + font-size: 13px; + font-family: inherit; + height: 40px; + box-sizing: border-box; + transition: all 0.15s; + + &::placeholder { color: $gray-500; } + + &:focus { + outline: none; + border-color: $primary; + background: $white; + box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1); + } + + &--sm { + width: auto; + padding: 6px 10px 6px 10px; + font-size: 12px; + height: 30px; + } +} + +/* Native + 8-digit DIN number from MCA + +
+ } + + + @if (verifying) { +
+ +
{{ progress }}%
+
+ @for (step of progressSteps; track step.label; let i = $index) { +
+ + {{ step.label }} +
+ } +
+

+ Verifying DIN {{ dinNumber }} for {{ signatoryName }} + against MCA (Ministry of Corporate Affairs) records. +

+
+ } + + + @if (!verifying && verified) { +
+
+ +
+
VERIFIED
+

DIN Verified Successfully

+

+ DIN {{ dinNumber }} for + {{ signatoryName }} has been verified against + MCA records. +

+
+
+ DIN + {{ dinNumber }} +
+ @if (directorName) { +
+ Director Name + {{ directorName }} +
+ } +
+ Status + {{ dinStatus || 'Active' }} +
+
+ Verified On + {{ verifiedDate }} +
+ @if (dinDesignation) { +
+ Designation + {{ dinDesignation }} +
+ } +
+ + + @if (companies.length > 0) { +
+

+ + Associated Companies ({{ companies.length }}) +

+ + @if (companyMatch) { +
+ +
+ Entity Match Found +

{{ entityName }} matches {{ matchedCompany }}

+
+
+ } @else { +
+ +
+ No Entity Match +

{{ entityName }} does not match any of the director's associated companies.

+
+
+ } + +
+ @for (company of companies; track company.companyName || $index) { +
+ @if (company._matched) { + check_circle + } @else { + radio_button_unchecked + } +
+ {{ company.companyName }} + + @if (company.cin) { CIN: {{ company.cin }} } + @if (company.cin && company.designation) { · } + @if (company.designation) { {{ company.designation }} } + @if ((company.cin || company.designation) && company.appointmentDate) { · } + @if (company.appointmentDate) { Since: {{ company.appointmentDate }} } + +
+
+ } +
+
+ } +
+ } + + + @if (!verifying && failed) { +
+
+ +
+
FAILED
+

DIN Verification Failed

+

+ DIN {{ dinNumber }} could not be verified. +

+ @if (failReason) { +
+ + {{ failReason }} +
+ } +
+ } + + + @if (!verifying && errorOccurred) { +
+ +

Verification failed. Please try again.

+

{{ errorMessage }}

+
+ } + + + + @if (!verifying && !verified && !failed && !errorOccurred) { + + + } + @if (!verifying && verified) { + + } + @if (!verifying && failed) { + + + } + @if (!verifying && errorOccurred) { + + + } + + `, + styles: [ + ` + :host { + display: block; + min-width: 520px; + } + + h2[mat-dialog-title] { + display: flex; + align-items: center; + gap: 8px; + margin: 0; + padding: 16px 24px; + font-size: 18px; + font-weight: 600; + color: #1074b9; + } + + .dialog-title-icon { font-size: 20px; } + + mat-dialog-content { + padding: 16px 24px; + max-height: 70vh; + overflow: auto; + } + + /* Signatory info */ + .signatory-info { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + background: #f0f4f8; + border-radius: 10px; + margin-bottom: 10px; + border: 1px solid #e2e8f0; + } + + .sig-icon { font-size: 22px; color: #1074b9; } + .sig-detail { display: flex; flex-direction: column; } + .sig-name { font-size: 15px; font-weight: 700; color: #1a2332; } + .sig-meta { font-size: 12px; color: #64748b; margin-top: 2px; } + + /* Entity info */ + .entity-info { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + background: #fffbeb; + border-radius: 8px; + margin-bottom: 14px; + border: 1px solid #fde68a; + } + + .entity-icon { font-size: 18px; color: #b45309; } + .entity-detail { display: flex; flex-direction: column; } + .entity-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: #92400e; } + .entity-name { font-size: 14px; font-weight: 700; color: #78350f; } + + /* DIN info bar */ + .din-info-bar { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px 14px; + background: #e3f2fd; + border-radius: 8px; + margin-bottom: 16px; + font-size: 13px; + color: #333; + line-height: 1.5; + } + + .info-icon { font-size: 16px; color: #1074b9; flex-shrink: 0; margin-top: 2px; } + + /* DIN input */ + .din-input-field { width: 100%; } + .din-prefix-icon { color: #64748b; margin-right: 4px; } + + /* Progress */ + .progress-section { display: flex; flex-direction: column; align-items: center; gap: 12px; } + .progress-percent { font-size: 22px; font-weight: 700; color: #1074b9; margin-top: 4px; } + + .progress-steps { display: flex; flex-direction: column; gap: 10px; width: 100%; padding: 8px 16px; } + .progress-step { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #9ca3af; transition: color 0.3s ease; } + .progress-step.active { color: #1074b9; font-weight: 600; } + .progress-step.done { color: #388e3c; } + .done-icon { color: #388e3c; } + + .verify-msg { font-size: 14px; color: #555; line-height: 1.6; text-align: center; margin: 4px 0 0; } + + /* Result cards */ + .result-card { display: flex; flex-direction: column; align-items: center; padding: 20px 16px; gap: 12px; } + .result-icon-ring { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } + .success-ring { background: linear-gradient(135deg, #ecfdf5, #d1fae5); border: 3px solid #a7f3d0; } + .fail-ring { background: linear-gradient(135deg, #fef2f2, #fecaca); border: 3px solid #fca5a5; } + .result-shield { font-size: 28px; } + .success-shield { color: #059669; } + .fail-shield { color: #dc2626; } + + .result-badge { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: 1.5px; padding: 3px 14px; border-radius: 20px; } + .success-badge { color: #059669; background: #ecfdf5; border: 1px solid #a7f3d0; } + .fail-badge { color: #dc2626; background: #fef2f2; border: 1px solid #fecaca; } + + .result-title { font-size: 17px; font-weight: 700; color: #111827; margin: 0; } + .result-detail { font-size: 13px; color: #6b7280; text-align: center; line-height: 1.6; margin: 0; max-width: 400px; } + .din-highlight { color: #059669; font-family: 'JetBrains Mono', 'Fira Code', monospace; } + .din-highlight-fail { color: #dc2626; font-family: 'JetBrains Mono', 'Fira Code', monospace; } + + .result-meta { width: 100%; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px 16px; margin-top: 4px; } + .meta-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #f3f4f6; } + .meta-row:last-child { border-bottom: none; } + .meta-label { font-size: 12px; color: #9ca3af; font-weight: 500; } + .meta-value { font-size: 12px; color: #374151; font-weight: 600; } + .meta-mono { font-family: 'JetBrains Mono', 'Fira Code', monospace; letter-spacing: 0.5px; } + .meta-success { color: #059669; } + + /* ═══ Company Match Section ═══ */ + .company-match-section { + width: 100%; + margin-top: 8px; + } + + .company-match-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 700; + color: #1e293b; + margin: 0 0 10px; + } + + .company-title-icon { font-size: 14px; color: #64748b; } + + /* Match banner */ + .match-banner { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + border-radius: 8px; + margin-bottom: 10px; + font-size: 13px; + line-height: 1.5; + } + + .match-banner p { margin: 2px 0 0; font-size: 12px; } + + .match-success { + background: #ecfdf5; + border: 1px solid #a7f3d0; + color: #065f46; + } + + .match-success .match-banner-icon { color: #059669; font-size: 18px; margin-top: 1px; flex-shrink: 0; } + + .match-warning { + background: #fffbeb; + border: 1px solid #fde68a; + color: #92400e; + } + + .match-warning .match-banner-icon { color: #d97706; font-size: 18px; margin-top: 1px; flex-shrink: 0; } + + /* Company list */ + .company-list { + border: 1px solid #e5e7eb; + border-radius: 8px; + overflow: hidden; + } + + .company-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + border-bottom: 1px solid #f3f4f6; + transition: background 0.15s; + } + + .company-item:last-child { border-bottom: none; } + + .company-item:hover { background: #f8fafc; } + + .company-matched { + background: #f0fdf4 !important; + border-left: 3px solid #22c55e; + } + + .company-match-icon { + font-size: 18px !important; + width: 18px !important; + height: 18px !important; + flex-shrink: 0; + } + + .company-match-icon.match { color: #22c55e; } + .company-match-icon.no-match { color: #d1d5db; } + + .company-info { display: flex; flex-direction: column; min-width: 0; } + .company-name { font-size: 13px; font-weight: 600; color: #1e293b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .company-meta { font-size: 11px; color: #94a3b8; margin-top: 2px; } + + /* Fail reason */ + .fail-reason { + display: flex; align-items: center; gap: 10px; width: 100%; + background: #fef2f2; border: 1px solid #fecaca; padding: 10px 14px; + border-radius: 6px; font-size: 13px; color: #991b1b; line-height: 1.5; + } + .fail-reason-icon { font-size: 16px; color: #dc2626; flex-shrink: 0; } + + /* Error */ + .error-icon-large { font-size: 48px; color: #f57c00; } + .error-msg { font-size: 16px; font-weight: 600; color: #f57c00; margin: 0; text-align: center; } + .error-detail { font-size: 14px; color: #555; text-align: center; } + + mat-dialog-actions { padding: 8px 24px 16px; } + mat-dialog-actions button { display: flex; align-items: center; gap: 6px; } + ` + ], + imports: [ + CommonModule, + FormsModule, + MatDialogModule, + MatProgressBarModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + FaIconComponent + ] +}) +export class DinVerifyDialogComponent implements OnInit, OnDestroy { + private dialogRef = inject(MatDialogRef); + private data: { + clientId: string; + kycId: string; + signatoryName: string; + cifNumber: string; + panNumber: string; + entityName: string; + } = inject(MAT_DIALOG_DATA); + private clientsService = inject(ClientsService); + + signatoryName = ''; + cifNumber = ''; + panNumber = ''; + entityName = ''; + dinNumber = ''; + + verifying = false; + verified = false; + failed = false; + errorOccurred = false; + errorMessage = ''; + failReason = ''; + + directorName = ''; + dinStatus = ''; + dinDesignation = ''; + verifiedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); + + /** Companies list from DIN verification response */ + companies: any[] = []; + /** Whether entity name matched a company */ + companyMatch = false; + /** The matched company name */ + matchedCompany = ''; + + progress = 0; + currentStep = 0; + progressSteps = [ + { label: 'Connecting to MCA portal...', icon: 'globe' as any }, + { label: 'Looking up DIN records...', icon: 'search' as any }, + { label: 'Matching entity with companies...', icon: 'building' as any }, + { label: 'Saving verification...', icon: 'database' as any } + ]; + + private intervalId: any; + private apiResponse: any = null; + private apiError: any = null; + private apiDone = false; + + ngOnInit() { + this.signatoryName = this.data?.signatoryName || ''; + this.cifNumber = this.data?.cifNumber || ''; + this.panNumber = this.data?.panNumber || '-'; + this.entityName = this.data?.entityName || ''; + } + + onVerify() { + if (!this.dinNumber || this.dinNumber.length < 8) return; + + this.verifying = true; + this.verified = false; + this.failed = false; + this.errorOccurred = false; + this.apiDone = false; + this.apiResponse = null; + this.apiError = null; + this.progress = 0; + this.currentStep = 0; + this.companies = []; + this.companyMatch = false; + this.matchedCompany = ''; + + this.dialogRef.disableClose = true; + + // Fire API call — pass clientId, kycId, dinNumber and entityName + this.clientsService.verifyDIN(this.data.clientId, this.data.kycId, this.dinNumber, this.entityName).subscribe({ + next: (res: any) => { + this.apiResponse = res; + this.apiDone = true; + this.tryShowResult(); + }, + error: (err: any) => { + this.apiError = err; + this.apiDone = true; + this.tryShowResult(); + } + }); + + // Animate progress + this.intervalId = setInterval(() => { + this.progress += 2; + this.currentStep = Math.min(Math.floor(this.progress / 25), this.progressSteps.length - 1); + if (this.progress >= 100) { + clearInterval(this.intervalId); + this.intervalId = null; + this.tryShowResult(); + } + }, 60); + } + + private tryShowResult() { + if (!this.apiDone || this.progress < 100) return; + + this.verifying = false; + this.dialogRef.disableClose = false; + + if (this.apiError) { + this.errorOccurred = true; + this.errorMessage = this.apiError?.error?.defaultUserMessage || this.apiError?.error?.message || this.apiError?.message || 'Unable to verify DIN. Please try again.'; + return; + } + + const res = this.apiResponse; + const changes = res?.changes || res; + + if (changes?.dinVerified || res?.dinVerified) { + this.verified = true; + this.directorName = changes?.directorName || res?.directorName || ''; + this.dinStatus = changes?.dinStatus || res?.dinStatus || 'Active'; + this.dinDesignation = changes?.dinDesignation || res?.dinDesignation || ''; + this.verifiedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); + + // Process company list from response + const rawCompanies = changes?.companies || res?.companies || []; + this.companies = rawCompanies.map((c: any) => ({ + ...c, + companyName: c.companyName || c.name || '', + cin: c.cin || c.cinNumber || '', + designation: c.designation || '', + appointmentDate: c.appointmentDate || c.dateOfAppointment || '', + _matched: false + })); + + // Match entity name against company list + this.matchCompanyName(); + } else { + this.failed = true; + this.failReason = changes?.dinFailReason || res?.failReason || res?.defaultUserMessage || 'DIN could not be verified against MCA records.'; + } + } + + /** Match the entity name against the list of companies from the DIN response */ + private matchCompanyName() { + if (!this.entityName || this.companies.length === 0) return; + + const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); + const entityNorm = normalize(this.entityName); + + for (const company of this.companies) { + const compNorm = normalize(company.companyName); + // Exact match or substring containment + if (compNorm === entityNorm || compNorm.includes(entityNorm) || entityNorm.includes(compNorm)) { + company._matched = true; + this.companyMatch = true; + this.matchedCompany = company.companyName; + break; + } + } + } + + onSkip() { + this.dialogRef.close({ skipped: true }); + } + + onRetry() { + this.verified = false; + this.failed = false; + this.errorOccurred = false; + this.progress = 0; + this.currentStep = 0; + this.companies = []; + this.companyMatch = false; + this.matchedCompany = ''; + } + + onClose(verified: boolean) { + this.dialogRef.close({ + verified, + dinNumber: this.dinNumber, + directorName: this.directorName, + dinStatus: this.dinStatus, + dinDesignation: this.dinDesignation, + dinVerified: verified, + companyMatch: this.companyMatch, + matchedCompany: this.matchedCompany, + companies: this.companies, + response: this.apiResponse + }); + } + + ngOnDestroy() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } +} diff --git a/src/app/clients/client-stepper/client-india-address-step/client-india-address-step.component.scss b/src/app/clients/client-stepper/client-india-address-step/client-india-address-step.component.scss index 2b0ca9c9a..443be4642 100644 --- a/src/app/clients/client-stepper/client-india-address-step/client-india-address-step.component.scss +++ b/src/app/clients/client-stepper/client-india-address-step/client-india-address-step.component.scss @@ -38,7 +38,7 @@ $muted: #64748b; &__sub { font-size: 11px; - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); margin: 2px 0 0; } } @@ -173,7 +173,7 @@ $muted: #64748b; } // Responsive -@media (max-width: 768px) { +@media (width <= 768px) { .addr-grid--2, .addr-grid--3 { grid-template-columns: 1fr; } diff --git a/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.html b/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.html index daee1a515..429978f87 100644 --- a/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.html +++ b/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.html @@ -305,23 +305,85 @@ - - - GSTIN Number - - @if (kycForm.get('gstinNumber')?.hasError('pattern') && kycForm.get('gstinNumber')?.touched) { - Invalid GSTIN format (e.g. 22AAAAA0000A1Z5) - } - + +
+ + GSTIN Number + receipt_long + + @if (kycForm.get('gstinNumber')?.hasError('pattern') && kycForm.get('gstinNumber')?.touched) { + Invalid GSTIN format (e.g. 22AAAAA0000A1Z5) + } + @if (gstinVerifyStatus === 'VERIFIED') { + check_circle + } + @if (gstinVerifyStatus === 'FAILED') { + cancel + } + +
+ @if (gstinVerifyStatus === 'NONE' || gstinVerifyStatus === 'FAILED') { + + } + @if (gstinVerifyStatus === 'VERIFIED') { + Verified + } +
+
+ @if (gstinVerifyStatus === 'VERIFIED') { +
+
+ + GSTIN Verified Successfully +
+
+ @if (gstinVerifyName) { +
+ Trade Name / Legal Name + {{ gstinVerifyName }} +
+ } + @if (gstinVerifyState) { +
+ State + {{ gstinVerifyState }} +
+ } +
+ Verified On + {{ gstinVerifiedOn }} +
+
+
+ } @if (constitutionMeta?.value === 'PROPRIETORSHIP') { diff --git a/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.ts b/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.ts index 857797484..e5b944138 100644 --- a/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.ts +++ b/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.ts @@ -263,6 +263,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy { entPanVerifyName = ''; entPanVerifiedOn = ''; + /** GSTIN verification */ + gstinVerifyStatus: 'NONE' | 'VERIFIED' | 'FAILED' = 'NONE'; + gstinVerifying = false; + gstinVerifyName = ''; + gstinVerifiedOn = ''; + gstinVerifyState = ''; + /** CIN verification (dummy) */ cinVerifyStatus: 'NONE' | 'VERIFIED' | 'FAILED' = 'NONE'; cinVerifying = false; @@ -480,6 +487,17 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy { if (data.panAadhaarSeedingStatus) { this.panAadhaarSeedingStatus = data.panAadhaarSeedingStatus; } + if (data.gstinVerified) { + this.gstinVerifyStatus = 'VERIFIED'; + this.gstinVerifyName = data.gstinTradeName || data.gstinLegalName || ''; + this.gstinVerifyState = data.gstinState || ''; + const rawGstinDate = data.gstinVerifiedOn; + if (Array.isArray(rawGstinDate) && rawGstinDate.length >= 3) { + this.gstinVerifiedOn = `${rawGstinDate[2]?.toString().padStart(2, '0')}/${rawGstinDate[1]?.toString().padStart(2, '0')}/${rawGstinDate[0]}`; + } else if (rawGstinDate) { + this.gstinVerifiedOn = rawGstinDate; + } + } } // Load existing photo & signature this.loadExistingPhoto(); @@ -772,6 +790,68 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy { ); } + /** Verify GSTIN via API */ + verifyGstin() { + if (!this.clientId) return; + const gstin = this.kycForm.value.gstinNumber; + if (!gstin || this.kycForm.get('gstinNumber')?.invalid) return; + + this.gstinVerifying = true; + const dateFormat = this.settingsService.dateFormat; + + const payload: any = { + locale: 'en', + kycType: 1, + riskCategory: this.kycForm.value.riskCategory || 1, + gstinNumber: gstin, + dateFormat, + panNumber: this.kycForm.value.enterprisePan || this.kycForm.value.panNumber || null, + aadhaarNumberMasked: 'XXXXXXXX0000' + }; + + this.saveKycAndDo( + payload, + () => { + this.clientsService.verifyGSTIN(this.clientId, this.kycId).subscribe({ + next: (res: any) => { + this.gstinVerifying = false; + const changes = res?.changes || res; + if (changes?.gstinVerified === true) { + this.gstinVerifyStatus = 'VERIFIED'; + this.gstinVerifyName = changes?.gstinTradeName || changes?.gstinLegalName || ''; + this.gstinVerifyState = changes?.gstinState || ''; + const rawDate = changes?.gstinVerifiedOn; + if (Array.isArray(rawDate)) { + this.gstinVerifiedOn = `${rawDate[2]?.toString().padStart(2, '0')}/${rawDate[1]?.toString().padStart(2, '0')}/${rawDate[0]}`; + } else { + const now = new Date(); + this.gstinVerifiedOn = + rawDate || + `${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`; + } + } else { + this.gstinVerifyStatus = 'FAILED'; + this.showPanErrorAlert( + changes?.gstinVerificationMessage || 'GSTIN verification failed. Please check the GSTIN number and try again.' + ); + } + }, + error: (err: any) => { + this.gstinVerifying = false; + this.gstinVerifyStatus = 'FAILED'; + this.showPanErrorAlert( + err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || 'GSTIN verification failed.' + ); + } + }); + }, + (msg) => { + this.gstinVerifying = false; + this.showPanErrorAlert(msg); + } + ); + } + /** Verify CIN (dummy — no real API) */ verifyCin() { const cin = this.kycForm.value.cinNumber; diff --git a/src/app/clients/client-stepper/client-kyc-step/image-crop-dialog/image-crop-dialog.component.scss b/src/app/clients/client-stepper/client-kyc-step/image-crop-dialog/image-crop-dialog.component.scss index b856b0a44..4086f3fc6 100644 --- a/src/app/clients/client-stepper/client-kyc-step/image-crop-dialog/image-crop-dialog.component.scss +++ b/src/app/clients/client-stepper/client-kyc-step/image-crop-dialog/image-crop-dialog.component.scss @@ -158,7 +158,7 @@ $navy: #0f172a; } } -@media (max-width: 600px) { +@media (width <= 600px) { .crop-workspace { flex-direction: column; } diff --git a/src/app/clients/client-stepper/client-preview-step/client-preview-step.component.scss b/src/app/clients/client-stepper/client-preview-step/client-preview-step.component.scss index 6fcd04dc7..acddf86ee 100644 --- a/src/app/clients/client-stepper/client-preview-step/client-preview-step.component.scss +++ b/src/app/clients/client-stepper/client-preview-step/client-preview-step.component.scss @@ -32,7 +32,7 @@ &__sub { font-size: 11px; - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); margin: 2px 0 0; } } @@ -51,7 +51,7 @@ padding: 20px 24px; background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%); border-radius: 10px; - border: 1px solid rgba(0, 0, 0, 0.06); + border: 1px solid rgb(0 0 0 / 6%); } .profile-avatar { @@ -96,9 +96,9 @@ font-weight: 500; padding: 3px 10px; border-radius: 20px; - background: rgba(255, 255, 255, 0.7); + background: rgb(255 255 255 / 70%); color: #555; - border: 1px solid rgba(0, 0, 0, 0.08); + border: 1px solid rgb(0 0 0 / 8%); .chip-icon { font-size: 10px; @@ -120,7 +120,7 @@ /* ── Info Cards ── */ .info-card { - border: 1px solid rgba(0, 0, 0, 0.08); + border: 1px solid rgb(0 0 0 / 8%); border-radius: 8px; overflow: hidden; background: #fff; @@ -132,7 +132,7 @@ gap: 8px; padding: 12px 20px; background: #fafafa; - border-bottom: 1px solid rgba(0, 0, 0, 0.06); + border-bottom: 1px solid rgb(0 0 0 / 6%); font-size: 14px; font-weight: 600; color: #333; @@ -177,8 +177,8 @@ flex-direction: column; gap: 4px; padding: 12px 16px; - border-bottom: 1px solid rgba(0, 0, 0, 0.04); - border-right: 1px solid rgba(0, 0, 0, 0.04); + border-bottom: 1px solid rgb(0 0 0 / 4%); + border-right: 1px solid rgb(0 0 0 / 4%); &:nth-child(3n) { border-right: none; @@ -195,7 +195,7 @@ font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; - color: rgba(0, 0, 0, 0.45); + color: rgb(0 0 0 / 45%); } .preview-value { @@ -204,7 +204,7 @@ color: #1a1a1a; &.no-data { - color: rgba(0, 0, 0, 0.3); + color: rgb(0 0 0 / 30%); font-style: italic; font-weight: 400; } @@ -239,7 +239,7 @@ border-collapse: collapse; tr { - border-bottom: 1px solid rgba(0, 0, 0, 0.04); + border-bottom: 1px solid rgb(0 0 0 / 4%); &:last-child { border-bottom: none; @@ -254,7 +254,7 @@ .info-label { width: 40%; font-size: 13px; - color: rgba(0, 0, 0, 0.5); + color: rgb(0 0 0 / 50%); font-weight: 500; padding-right: 16px; } @@ -278,17 +278,17 @@ padding: 10px 16px; font-size: 12px; font-weight: 600; - color: rgba(0, 0, 0, 0.55); + color: rgb(0 0 0 / 55%); text-align: left; text-transform: uppercase; letter-spacing: 0.3px; - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } } tbody { tr { - border-bottom: 1px solid rgba(0, 0, 0, 0.04); + border-bottom: 1px solid rgb(0 0 0 / 4%); &:last-child { border-bottom: none; @@ -349,7 +349,7 @@ .address-block { margin-bottom: 16px; padding-bottom: 16px; - border-bottom: 1px dashed rgba(0, 0, 0, 0.08); + border-bottom: 1px dashed rgb(0 0 0 / 8%); &:last-child { margin-bottom: 0; @@ -381,7 +381,7 @@ justify-content: flex-end; gap: 12px; padding: 20px 0 4px; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); margin-top: 8px; } @@ -394,23 +394,23 @@ } .cancel-btn { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); height: 44px; } /* ── Responsive ── */ -@media (max-width: 960px) { +@media (width <= 960px) { .preview-grid { grid-template-columns: 1fr 1fr; } .preview-field { - &:nth-child(3n) { border-right: 1px solid rgba(0, 0, 0, 0.04); } + &:nth-child(3n) { border-right: 1px solid rgb(0 0 0 / 4%); } &:nth-child(2n) { border-right: none; } } } -@media (max-width: 768px) { +@media (width <= 768px) { .profile-card { flex-direction: column; text-align: center; diff --git a/src/app/clients/clients-view/address-tab/address-tab.component.scss b/src/app/clients/clients-view/address-tab/address-tab.component.scss index 8166ba235..65b357191 100644 --- a/src/app/clients/clients-view/address-tab/address-tab.component.scss +++ b/src/app/clients/clients-view/address-tab/address-tab.component.scss @@ -92,7 +92,7 @@ $red-light: #fce8e6; margin-bottom: 12px !important; border-radius: 10px !important; border: 1px solid $gray-200 !important; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) !important; + box-shadow: 0 1px 3px rgb(0 0 0 / 6%) !important; &::ng-deep .mat-expansion-panel-body { padding: 0 20px 20px !important; @@ -194,11 +194,11 @@ $red-light: #fce8e6; border-radius: 8px; overflow: hidden; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: repeat(2, 1fr); } - @media (max-width: 480px) { + @media (width <= 480px) { grid-template-columns: 1fr; } } @@ -233,7 +233,7 @@ $red-light: #fce8e6; word-break: break-word; &--mono { - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; font-size: 13px; letter-spacing: 0.5px; } diff --git a/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.html b/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.html index 813399750..efd92a83b 100644 --- a/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.html +++ b/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.html @@ -24,52 +24,108 @@
- - - -
- -
- {{ 'labels.inputs.Account Number' | translate }} - - - @for (account of allAccounts; track account.accountNo) { - - {{ account.accountNo }} ({{ account.productName }}) - - } - - -
- - -
- {{ 'labels.inputs.Client ID' | translate }} - {{ clientId }} -
- - -
- {{ 'labels.inputs.Client Name' | translate }} - {{ clientName }} -
- - -
- {{ 'labels.inputs.Account Type' | translate }} - {{ selectedAccountType || '—' }} -
+ +
+
+
+ @if (ckycNumber) { + + } @else { + + }
- - +
+
+ @if (ckycNumber) { + SYNCED WITH CERSAI + } @else { + PENDING CERSAI SYNC + } +
+ @if (ckycNumber) { +
CKYC Registry Number
+
{{ ckycNumber }}
+
+ @if (ckycUploadedOn) { + + + Uploaded on {{ ckycUploadedOn }} + + } + @if (ckycBatchId) { + · + + + Batch {{ ckycBatchId }} + + } +
+ } @else { +
This client has not yet been synced to the CERSAI registry
+
+ Collect all 4 artefacts below, then create a batch from the Record Master to upload. +
+ } +
+
+ + @if (ckycNumber) { +
+ + Live +
+ } +
+ + +
+
+ + CIF Number + + {{ cifNumber || clientId }} +
+ +
+ + Full Name + + {{ clientName || '—' }} +
+ +
+ + Email + + @if (clientEmail) { + {{ clientEmail }} + } @else { + No email on file + } +
+ +
+ + Mobile + + @if (clientMobile) { + {{ clientMobile }} + } @else { + No mobile on file + } +
+

{{ 'labels.heading.CKYC Documents' | translate }}

+ @if (checkingExisting) { +
+ + Checking for already-uploaded artefacts… +
+ } +
@@ -78,6 +134,41 @@ home {{ 'labels.inputs.Address Proof Document' | translate }}
+ + @if (existing.ADDRESS_PROOF.present) { +
+
+ +
+ Already available + from {{ existing.ADDRESS_PROOF.source }} +
+
+
+ + {{ existing.ADDRESS_PROOF.name }} + @if (existing.ADDRESS_PROOF.documentKey) { + {{ existing.ADDRESS_PROOF.documentKey }} + } + @if (existing.ADDRESS_PROOF.fileCount && existing.ADDRESS_PROOF.fileCount > 0) { + + {{ existing.ADDRESS_PROOF.fileCount }} {{ existing.ADDRESS_PROOF.fileCount === 1 ? 'file' : 'files' }} + + } +
+ +
+ } + + @if (!existing.ADDRESS_PROOF.present || existing.ADDRESS_PROOF.expanded) {

{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF

} + } @@ -147,6 +239,41 @@ photo_camera {{ 'labels.inputs.Photo Proof Document' | translate }}
+ + @if (existing.PHOTO_PROOF.present) { +
+
+ +
+ Already available + from {{ existing.PHOTO_PROOF.source }} +
+
+
+ + {{ existing.PHOTO_PROOF.name }} + @if (existing.PHOTO_PROOF.documentKey) { + {{ existing.PHOTO_PROOF.documentKey }} + } + @if (existing.PHOTO_PROOF.fileCount && existing.PHOTO_PROOF.fileCount > 0) { + + {{ existing.PHOTO_PROOF.fileCount }} {{ existing.PHOTO_PROOF.fileCount === 1 ? 'file' : 'files' }} + + } +
+ +
+ } + + @if (!existing.PHOTO_PROOF.present || existing.PHOTO_PROOF.expanded) {

{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF

} + }
@@ -216,6 +344,32 @@ person {{ 'labels.inputs.Image of Client' | translate }}
+ + @if (existing.CLIENT_IMAGE.present) { +
+
+ +
+ Already available + from {{ existing.CLIENT_IMAGE.source }} +
+
+ @if (existing.CLIENT_IMAGE.previewUrl) { + Client photo + } + +
+ } + + @if (!existing.CLIENT_IMAGE.present || existing.CLIENT_IMAGE.expanded) {

{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG

} + } @@ -285,6 +440,36 @@ draw {{ 'labels.inputs.Signature of Client' | translate }}
+ + @if (existing.CLIENT_SIGNATURE.present) { +
+
+ +
+ Already available + from {{ existing.CLIENT_SIGNATURE.source }} +
+
+ @if (existing.CLIENT_SIGNATURE.previewUrl) { + Client signature + } +
+ + {{ existing.CLIENT_SIGNATURE.name }} +
+ +
+ } + + @if (!existing.CLIENT_SIGNATURE.present || existing.CLIENT_SIGNATURE.expanded) {

{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG

} + }
diff --git a/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.scss b/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.scss index 1b5d6650d..efcb3347e 100644 --- a/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.scss +++ b/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.scss @@ -49,37 +49,236 @@ line-height: 18px; } -.ckyc-info-card { - margin-bottom: 24px; +/* ===================== CERSAI Sync Hero ===================== */ +.cersai-hero { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 22px 26px; + border-radius: 16px; + overflow: hidden; + margin-bottom: 18px; + color: #fff; + box-shadow: 0 6px 22px rgba(0, 0, 0, 0.1); + + &::before { + content: ''; + position: absolute; + top: -60%; + right: -10%; + width: 320px; + height: 320px; + border-radius: 50%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.18), transparent 70%); + pointer-events: none; + } + + &--synced { + background: linear-gradient(135deg, #0f6e56 0%, #1d9e75 55%, #2fb88c 100%); + } + + &--pending { + background: linear-gradient(135deg, #8a5a14 0%, #ba7517 55%, #d4892f 100%); + } + + &__left { + display: flex; + align-items: center; + gap: 18px; + min-width: 0; + position: relative; + flex: 1; + } + + &__icon { + width: 60px; + height: 60px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.35); + display: flex; + align-items: center; + justify-content: center; + font-size: 26px; + flex-shrink: 0; + backdrop-filter: blur(10px); + } + + &__text { + min-width: 0; + flex: 1; + } + + &__tag { + display: inline-block; + padding: 4px 12px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.22); + border: 1px solid rgba(255, 255, 255, 0.4); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.8px; + text-transform: uppercase; + backdrop-filter: blur(8px); + } + + &__ckyc-label { + font-size: 11px; + font-weight: 600; + opacity: 0.9; + text-transform: uppercase; + letter-spacing: 0.5px; + margin: 10px 0 4px; + } + + &__ckyc-number { + font-size: 24px; + font-weight: 800; + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; + letter-spacing: 0.5px; + line-height: 1.1; + margin-bottom: 8px; + } + + &__meta { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + opacity: 0.95; + flex-wrap: wrap; + + fa-icon { font-size: 11px; opacity: 0.9; margin-right: 3px; } + strong { font-weight: 700; } + } + + &__sep { opacity: 0.5; } + + &__hint { + font-size: 12px; + opacity: 0.9; + line-height: 1.5; + max-width: 480px; + margin-top: 4px; + } + + &__pulse { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 7px 14px; + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.35); + border-radius: 20px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.5px; + backdrop-filter: blur(8px); + flex-shrink: 0; + position: relative; + z-index: 1; + } + + &__pulse-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #9fe1cb; + box-shadow: 0 0 0 0 rgba(159, 225, 203, 0.7); + animation: cersai-pulse 2s infinite; + } } +@keyframes cersai-pulse { + 0% { box-shadow: 0 0 0 0 rgba(159, 225, 203, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(159, 225, 203, 0); } + 100% { box-shadow: 0 0 0 0 rgba(159, 225, 203, 0); } +} + +/* ===================== Client info grid ===================== */ .ckyc-info-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: 16px; - align-items: start; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-bottom: 22px; } .ckyc-info-item { display: flex; flex-direction: column; - gap: 4px; + gap: 6px; + padding: 14px 16px; + background: #fff; + border: 1px solid #e8eaed; + border-radius: 12px; + min-width: 0; + transition: all 0.15s; + + &:hover { + border-color: #c6dafc; + box-shadow: 0 2px 8px rgba(26, 115, 232, 0.06); + } +} + +@media (width <= 900px) { + .ckyc-info-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .cersai-hero { + flex-direction: column; + align-items: flex-start; + + &__left { width: 100%; } + &__icon { width: 52px; height: 52px; font-size: 22px; } + &__ckyc-number { font-size: 20px; } + } +} + +@media (width <= 500px) { + .ckyc-info-grid { + grid-template-columns: 1fr; + } } .ckyc-info-label { - font-size: 11px; - font-weight: 600; - color: #888; + font-size: 10px; + font-weight: 700; + color: #5f6368; text-transform: uppercase; - letter-spacing: 0.5px; + letter-spacing: 0.06em; + display: inline-flex; + align-items: center; + gap: 6px; + + fa-icon { color: #1a73e8; font-size: 11px; } } .ckyc-info-value { - font-size: 14px; - color: #333; - min-height: 20px; + font-size: 13px; + color: #202124; + font-weight: 600; + min-height: 18px; + word-break: break-word; + + &--mono { + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; + font-size: 12px; + color: #1a73e8; + letter-spacing: 0.3px; + } + + &--missing { + color: #9aa0a6; + font-weight: 500; + font-style: italic; + } } +.ckyc-info-hint { display: none; } + .ckyc-section-title { margin: 24px 0 12px; font-weight: 600; @@ -87,31 +286,58 @@ .ckyc-upload-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 16px; + align-items: stretch; } .ckyc-upload-card { + border-radius: 12px !important; + border: 1px solid #e8eaed; + box-shadow: none !important; + transition: border-color 0.15s, box-shadow 0.15s; + display: flex; + flex-direction: column; + min-height: 240px; + + &:hover { + border-color: #c6dafc; + box-shadow: 0 2px 10px rgba(26, 115, 232, 0.06) !important; + } + .mat-mdc-card-content { - padding: 16px; + padding: 18px 20px !important; + display: flex; + flex-direction: column; + height: 100%; } } .ckyc-upload-header { display: flex; align-items: center; - gap: 8px; + gap: 10px; font-weight: 600; font-size: 14px; - margin-bottom: 8px; + color: #202124; + padding-bottom: 12px; + margin-bottom: 0; + border-bottom: 1px solid #f1f3f4; } .ckyc-upload-icon { - font-size: 20px; - height: 20px; - width: 20px; - color: #1976d2; + font-size: 18px; + height: 32px; + width: 32px; + color: #fff; + background: linear-gradient(135deg, #1a73e8, #4285f4); + border-radius: 8px; + display: flex !important; + align-items: center; + justify-content: center; + box-shadow: 0 2px 6px rgba(26, 115, 232, 0.25); + flex-shrink: 0; } .ckyc-upload-hint { @@ -163,6 +389,12 @@ } } +@media (width <= 900px) { + .ckyc-upload-grid { + grid-template-columns: 1fr; + } +} + @media (width <= 600px) { .ckyc-info-grid { grid-template-columns: 1fr; @@ -180,3 +412,149 @@ width: 100%; } } + +/* ===================== Already-available banners ===================== */ + +.ckyc-existing-loading { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 8px 14px; + background: #e8f0fe; + color: #0c447c; + border-radius: 8px; + font-size: 12px; + margin-bottom: 14px; + + mat-progress-spinner { display: inline-block; } +} + +.ckyc-existing { + display: flex; + flex-direction: column; + gap: 12px; + padding: 14px 14px 12px; + background: #f0fbf6; + border: 1px solid #cfecdd; + border-radius: 10px; + margin-top: 14px; + flex: 1; + position: relative; + + &::before { + content: ''; + position: absolute; + inset: 0 auto 0 0; + width: 3px; + background: #1d9e75; + border-radius: 10px 0 0 10px; + } + + &__head { + display: flex; + align-items: center; + gap: 10px; + + strong { + display: block; + font-size: 13px; + font-weight: 700; + color: #085041; + line-height: 1.2; + } + } + + &__ok { + font-size: 20px; + color: #1d9e75; + flex-shrink: 0; + } + + &__source { + display: block; + font-size: 11px; + color: #0f6e56; + margin-top: 2px; + font-weight: 500; + } + + &__meta { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: #085041; + flex-wrap: wrap; + padding: 8px 10px; + background: #fff; + border: 1px solid #cfecdd; + border-radius: 8px; + + > span:first-of-type { + font-weight: 600; + } + + fa-icon { color: #1d9e75; font-size: 12px; } + } + + &__key { + font-family: 'SF Mono', Consolas, monospace; + font-size: 11px; + background: #e1f5ee; + color: #0f6e56; + padding: 2px 8px; + border-radius: 10px; + border: 1px solid #cfecdd; + font-weight: 600; + } + + &__count { + font-size: 10px; + font-weight: 700; + background: #1d9e75; + color: #fff; + padding: 2px 8px; + border-radius: 10px; + text-transform: uppercase; + letter-spacing: 0.3px; + } + + &__thumb { + width: 100px; + height: 100px; + border-radius: 10px; + border: 2px solid #fff; + outline: 1px solid #cfecdd; + background: #fff; + object-fit: cover; + align-self: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + + &--wide { + width: 200px; + height: 80px; + object-fit: contain; + } + } + + &__replace { + align-self: stretch; + margin-top: auto; + height: 34px !important; + font-size: 11px !important; + font-weight: 600 !important; + border-radius: 8px !important; + border: 1px solid #cfecdd !important; + color: #0f6e56 !important; + background: #fff !important; + padding: 0 12px !important; + letter-spacing: 0.2px; + + fa-icon { margin-right: 6px; font-size: 11px; } + + &:hover { + background: #e1f5ee !important; + border-color: #1d9e75 !important; + } + } +} diff --git a/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.ts b/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.ts index 5f66d01bc..2033c44e8 100644 --- a/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.ts +++ b/src/app/clients/clients-view/ckyc-tab/ckyc-tab.component.ts @@ -9,7 +9,7 @@ /** Angular Imports */ import { Component, OnInit, OnDestroy, inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Subject, takeUntil, catchError, of } from 'rxjs'; +import { Subject, takeUntil, catchError, of, forkJoin } from 'rxjs'; /** Custom Services */ import { ClientsService } from '../../clients.service'; @@ -19,6 +19,17 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +interface ExistingArtefact { + present: boolean; + name?: string; + source?: string; + previewUrl?: string; + documentId?: string; + documentKey?: string; + fileCount?: number; + expanded?: boolean; +} + @Component({ selector: 'mifosx-ckyc-tab', templateUrl: './ckyc-tab.component.html', @@ -41,6 +52,10 @@ export class CkycTabComponent implements OnInit, OnDestroy { /** Top section - read-only client info */ clientId: string; clientName: string = ''; + clientEmail: string = ''; + clientMobile: string = ''; + cifNumber: string = ''; + ckycNumber: string = ''; allAccounts: any[] = []; selectedAccount: any = null; selectedAccountType: string = ''; @@ -48,6 +63,17 @@ export class CkycTabComponent implements OnInit, OnDestroy { /** State */ loading = true; ckycData: any = null; + ckycUploadedOn = ''; + ckycBatchId = ''; + checkingExisting = false; + + /** Pre-existing artefacts detected from client data */ + existing: Record<'ADDRESS_PROOF' | 'PHOTO_PROOF' | 'CLIENT_IMAGE' | 'CLIENT_SIGNATURE', ExistingArtefact> = { + ADDRESS_PROOF: { present: false }, + PHOTO_PROOF: { present: false }, + CLIENT_IMAGE: { present: false }, + CLIENT_SIGNATURE: { present: false } + }; /** File references */ addressProofFile: File | null = null; @@ -80,13 +106,18 @@ export class CkycTabComponent implements OnInit, OnDestroy { this.loadClientInfo(); this.loadAccounts(); this.loadCkycData(); + this.loadExistingArtefacts(); }); } private loadClientInfo(): void { this.route.parent.data.subscribe((data: any) => { - if (data?.clientViewData) { - this.clientName = data.clientViewData.displayName || ''; + const cv = data?.clientViewData; + if (cv) { + this.clientName = cv.displayName || ''; + this.clientEmail = cv.emailAddress || ''; + this.clientMobile = cv.mobileNo || ''; + this.cifNumber = cv.accountNo || cv.externalId || ''; } }); } @@ -122,8 +153,10 @@ export class CkycTabComponent implements OnInit, OnDestroy { private loadCkycData(): void { this.loading = true; + // Read CKYC registry data from /clients/{id}/kyc — that's where the + // SFTP upload flow persists ckycNumber / ckycUploadedOn / ckycBatchId. this.clientsService - .getClientCkyc(this.clientId) + .getClientKyc(this.clientId) .pipe( takeUntil(this.destroy$), catchError(() => { @@ -134,9 +167,120 @@ export class CkycTabComponent implements OnInit, OnDestroy { .subscribe((data: any) => { this.loading = false; this.ckycData = data; + this.ckycNumber = data?.ckycNumber || ''; + this.ckycUploadedOn = this.formatJavaDate(data?.ckycUploadedOn); + this.ckycBatchId = data?.ckycBatchId || ''; }); } + private formatJavaDate(value: any): string { + if (!value) return ''; + if (Array.isArray(value) && value.length >= 3) { + const [y, m, d] = value; + return `${String(d).padStart(2, '0')}-${String(m).padStart(2, '0')}-${y}`; + } + if (typeof value === 'string') return value; + return ''; + } + + /** + * Fetch photo (profile image), signature (client document), + * ID proof and address proof (client identifiers) so already-uploaded + * artefacts are shown here instead of forcing re-upload. + */ + private loadExistingArtefacts(): void { + this.checkingExisting = true; + + forkJoin({ + photo: this.clientsService.getClientProfileImage(this.clientId).pipe(catchError(() => of(null))), + documents: this.clientsService.getClientDocuments(this.clientId).pipe(catchError(() => of([]))), + identifiers: this.clientsService.getClientIdentifiers(this.clientId).pipe(catchError(() => of([]))), + proofTypes: this.clientsService.getKycProofTypes().pipe(catchError(() => of({ idProofs: [], addressProofs: [] }))) + }) + .pipe(takeUntil(this.destroy$)) + .subscribe(({ photo, documents, identifiers, proofTypes }) => { + this.checkingExisting = false; + + // 1. Photo / image of client — from m_client.image_id + if (photo && typeof photo === 'string' && photo.length > 0) { + this.existing.CLIENT_IMAGE = { + present: true, + source: 'Client registration', + previewUrl: photo, + name: 'Profile photo' + }; + } + + // 2. Signature — client document with name='clientSignature' + const signatureDoc = (documents as any[])?.find((d: any) => d?.name === 'clientSignature'); + if (signatureDoc) { + this.existing.CLIENT_SIGNATURE = { + present: true, + source: 'Client registration', + name: signatureDoc.fileName || 'Client signature', + documentId: signatureDoc.id + }; + // Fetch blob for preview + this.clientsService + .getClientSignatureImage(this.clientId, signatureDoc.id) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (blob: any) => { + if (blob) { + this.existing.CLIENT_SIGNATURE.previewUrl = URL.createObjectURL(blob); + } + }, + error: () => undefined + }); + } + + // 3 & 4. ID proof + Address proof — client identifiers classified via proof-types + const idProofIds = new Set((proofTypes?.idProofs || []).map((p: any) => String(p.id))); + const addressProofIds = new Set( + (proofTypes?.addressProofs || []).map((p: any) => String(p.id)) + ); + + const idList = Array.isArray(identifiers) + ? identifiers + : (identifiers as any)?.pageItems || []; + + for (const identity of idList) { + const docTypeId = String(identity?.documentType?.id ?? ''); + const isActive = + !identity?.status || + identity?.status?.code === 'clientIdentifierStatusType.active' || + identity?.status === 'clientIdentifierStatusType.active' || + identity?.status === 'Active'; + if (!isActive) continue; + + const proofName = identity?.documentType?.name || 'Identifier'; + // Address proof documentKey is auto-generated as " - " + // in identities-tab; hide it when it's just that synthetic key. + const rawKey = identity?.documentKey || ''; + const isSyntheticKey = rawKey.startsWith(`${proofName} - `); + const common = { + present: true, + source: 'Identities section', + name: proofName, + documentKey: isSyntheticKey ? '' : rawKey, + documentId: identity?.id, + fileCount: (identity?.documents || []).length + }; + + if (idProofIds.has(docTypeId) && !this.existing.PHOTO_PROOF.present) { + this.existing.PHOTO_PROOF = common; + } else if (addressProofIds.has(docTypeId) && !this.existing.ADDRESS_PROOF.present) { + this.existing.ADDRESS_PROOF = common; + } + } + }); + } + + toggleReplace(documentType: string): void { + const entry = (this.existing as any)[documentType]; + if (entry) entry.expanded = !entry.expanded; + } + onAccountSelect(accountNo: string): void { this.selectedAccount = this.allAccounts.find((a) => a.accountNo === accountNo) || null; this.selectedAccountType = this.selectedAccount?.accountType || ''; @@ -215,5 +359,9 @@ export class CkycTabComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); + // Release object URLs + Object.values(this.existing).forEach((e) => { + if (e.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(e.previewUrl); + }); } } diff --git a/src/app/clients/clients-view/clients-view.component.scss b/src/app/clients/clients-view/clients-view.component.scss index 8db937770..ac77f001c 100644 --- a/src/app/clients/clients-view/clients-view.component.scss +++ b/src/app/clients/clients-view/clients-view.component.scss @@ -22,8 +22,8 @@ $accent: #0ea5e9; background: #fff !important; border-radius: 10px; overflow: hidden; - border: 1px solid rgba(0, 0, 0, 0.06); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 6%); + box-shadow: 0 1px 4px rgb(0 0 0 / 4%); margin: 0; padding: 0; } @@ -42,10 +42,7 @@ $accent: #0ea5e9; &::after { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background: repeating-linear-gradient(108deg, transparent 0, transparent 80px, rgb(255 255 255 / 2.5%) 80px, rgb(255 255 255 / 2.5%) 160px); pointer-events: none; } @@ -69,7 +66,7 @@ $accent: #0ea5e9; height: 72px; border-radius: 10px; object-fit: cover; - border: 2px solid rgba(255, 255, 255, 0.25); + border: 2px solid rgb(255 255 255 / 25%); background: #fff; } @@ -84,9 +81,9 @@ $accent: #0ea5e9; min-width: 26px !important; padding: 0 !important; border-radius: 5px !important; - background: rgba(255, 255, 255, 0.1) !important; - color: rgba(255, 255, 255, 0.6) !important; - border: 1px solid rgba(255, 255, 255, 0.15) !important; + background: rgb(255 255 255 / 10%) !important; + color: rgb(255 255 255 / 60%) !important; + border: 1px solid rgb(255 255 255 / 15%) !important; display: flex !important; align-items: center; justify-content: center; @@ -96,19 +93,19 @@ $accent: #0ea5e9; i { font-size: 10px; } &:hover { - background: rgba(255, 255, 255, 0.2) !important; + background: rgb(255 255 255 / 20%) !important; color: #fff !important; } &--danger:hover { - background: rgba(239, 68, 68, 0.35) !important; - border-color: rgba(239, 68, 68, 0.45) !important; + background: rgb(239 68 68 / 35%) !important; + border-color: rgb(239 68 68 / 45%) !important; } } &__sig { font-size: 10px; - color: rgba(255, 255, 255, 0.6); + color: rgb(255 255 255 / 60%); cursor: pointer; font-weight: 500; @@ -148,23 +145,23 @@ $accent: #0ea5e9; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.4px; - background: rgba(16, 185, 129, 0.2); + background: rgb(16 185 129 / 20%); color: #6ee7b7; - border: 1px solid rgba(16, 185, 129, 0.25); + border: 1px solid rgb(16 185 129 / 25%); } } .cv-menu-btn { - color: rgba(255, 255, 255, 0.7) !important; + color: rgb(255 255 255 / 70%) !important; width: 28px !important; height: 28px !important; min-width: 28px !important; padding: 0 !important; - border: 1px solid rgba(255, 255, 255, 0.15); + border: 1px solid rgb(255 255 255 / 15%); border-radius: 5px !important; line-height: 28px !important; - &:hover { background: rgba(255, 255, 255, 0.1); color: #fff !important; } + &:hover { background: rgb(255 255 255 / 10%); color: #fff !important; } .actions-menu { font-size: 14px !important; @@ -187,14 +184,14 @@ $accent: #0ea5e9; align-items: center; gap: 8px; padding: 4px 11px; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.13); + background: rgb(255 255 255 / 10%); + border: 1px solid rgb(255 255 255 / 13%); border-radius: 5px; &__label { font-size: 9px; font-weight: 700; - color: rgba(255, 255, 255, 0.4); + color: rgb(255 255 255 / 40%); text-transform: uppercase; letter-spacing: 0.4px; } @@ -203,7 +200,7 @@ $accent: #0ea5e9; font-size: 12px; font-weight: 600; color: #fff; - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; letter-spacing: 0.3px; } } @@ -220,19 +217,19 @@ $accent: #0ea5e9; .cv-cell { padding: 9px 13px; - background: rgba(255, 255, 255, 0.07); + background: rgb(255 255 255 / 7%); border-radius: 6px; - border: 1px solid rgba(255, 255, 255, 0.08); + border: 1px solid rgb(255 255 255 / 8%); transition: background 0.12s; &:hover { - background: rgba(255, 255, 255, 0.12); + background: rgb(255 255 255 / 12%); } &__label { font-size: 9px; font-weight: 600; - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; @@ -253,9 +250,9 @@ $accent: #0ea5e9; border-radius: 10px; letter-spacing: 0.3px; - &--green { background: rgba(16, 185, 129, 0.2); color: #6ee7b7; } - &--yellow { background: rgba(245, 158, 11, 0.2); color: #fcd34d; } - &--red { background: rgba(239, 68, 68, 0.2); color: #fca5a5; } + &--green { background: rgb(16 185 129 / 20%); color: #6ee7b7; } + &--yellow { background: rgb(245 158 11 / 20%); color: #fcd34d; } + &--red { background: rgb(239 68 68 / 20%); color: #fca5a5; } } .actions-menu { color: #fff !important; } @@ -263,7 +260,7 @@ $accent: #0ea5e9; // ── Tab bar ── .cv-tabs { background: #fff; - border-bottom: 1px solid rgba(0, 0, 0, 0.06); + border-bottom: 1px solid rgb(0 0 0 / 6%); ::ng-deep .mat-mdc-tab-link { font-size: 12px !important; @@ -292,7 +289,7 @@ $accent: #0ea5e9; button, mat-icon, fa-icon { - color: rgba(255, 255, 255, 0.4) !important; + color: rgb(255 255 255 / 40%) !important; font-size: 11px !important; width: 16px !important; height: 16px !important; @@ -311,8 +308,8 @@ $accent: #0ea5e9; // ── Client menu styling ── ::ng-deep .mat-mdc-menu-panel { border-radius: 10px !important; - border: 1px solid rgba(0, 0, 0, 0.06) !important; - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12) !important; + border: 1px solid rgb(0 0 0 / 6%) !important; + box-shadow: 0 8px 30px rgb(0 0 0 / 12%) !important; min-width: 200px !important; padding: 6px 0 !important; @@ -352,17 +349,17 @@ $accent: #0ea5e9; } // ── Responsive ── -@media (max-width: 1024px) { +@media (width <= 1024px) { .cv-grid { grid-template-columns: repeat(3, 1fr); } } -@media (max-width: 768px) { +@media (width <= 768px) { .cv-top { flex-direction: column; align-items: center; text-align: center; } .cv-name-row { justify-content: center; } .cv-ids { justify-content: center; } .cv-grid { grid-template-columns: repeat(2, 1fr); } } -@media (max-width: 480px) { +@media (width <= 480px) { .cv-grid { grid-template-columns: 1fr; } } diff --git a/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.html b/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.html new file mode 100644 index 000000000..18999db9c --- /dev/null +++ b/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.html @@ -0,0 +1,28 @@ + + +

{{ title }}

+ +

Drag the corners to crop. The result will be saved as a PNG.

+
+ +
+
+ + + + diff --git a/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.scss b/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.scss new file mode 100644 index 000000000..eb240c96e --- /dev/null +++ b/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.scss @@ -0,0 +1,52 @@ +/** + * 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/. + */ + +:host { + display: block; + min-width: 480px; + max-width: 720px; +} + +.icd-title { + font-size: 16px; + font-weight: 600; + margin: 0; + padding: 16px 20px 8px; + color: #0f172a; +} + +.icd-body { + padding: 0 20px 12px; +} + +.icd-hint { + font-size: 12px; + color: #64748b; + margin: 0 0 10px; +} + +.icd-cropper { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 8px; + max-height: 60vh; + overflow: hidden; + + ::ng-deep image-cropper { + display: block; + max-height: 56vh; + } +} + +.icd-actions { + padding: 8px 20px 16px; + display: flex; + justify-content: flex-end; + gap: 8px; +} diff --git a/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.ts b/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.ts new file mode 100644 index 000000000..7ea1210f3 --- /dev/null +++ b/src/app/clients/clients-view/custom-dialogs/image-cropper-dialog/image-cropper-dialog.component.ts @@ -0,0 +1,93 @@ +/** + * 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, inject } from '@angular/core'; +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialogTitle, + MatDialogActions, + MatDialogContent, + MatDialogClose +} from '@angular/material/dialog'; +import { CommonModule } from '@angular/common'; +import { ImageCropperComponent, ImageCroppedEvent, LoadedImage } from 'ngx-image-cropper'; +import { MatButton } from '@angular/material/button'; + +interface CropperDialogData { + file: File; + title?: string; +} + +@Component({ + selector: 'mifosx-image-cropper-dialog', + templateUrl: './image-cropper-dialog.component.html', + styleUrls: ['./image-cropper-dialog.component.scss'], + imports: [ + CommonModule, + ImageCropperComponent, + MatDialogTitle, + MatDialogContent, + MatDialogActions, + MatDialogClose, + MatButton + ] +}) +export class ImageCropperDialogComponent { + dialogRef = inject>(MatDialogRef); + data = inject(MAT_DIALOG_DATA); + + croppedBlob: Blob | null = null; + loading = true; + title = ''; + + constructor() { + this.title = this.data?.title || 'Crop image'; + } + + imageCropped(event: ImageCroppedEvent): void { + this.croppedBlob = event.blob || null; + } + + imageLoaded(_image: LoadedImage): void { + this.loading = false; + } + + loadImageFailed(): void { + this.loading = false; + this.dialogRef.close(null); + } + + save(): void { + if (!this.croppedBlob) { + this.dialogRef.close(null); + return; + } + const ext = this.guessExt(this.data.file.name); + const cropped = new File([this.croppedBlob], `${this.stripExt(this.data.file.name)}-cropped.${ext}`, { + type: this.croppedBlob.type || 'image/png' + }); + this.dialogRef.close(cropped); + } + + cancel(): void { + this.dialogRef.close(null); + } + + private stripExt(name: string): string { + const i = name.lastIndexOf('.'); + return i > 0 ? name.slice(0, i) : name; + } + + private guessExt(name: string): string { + const i = name.lastIndexOf('.'); + if (i < 0) return 'png'; + const ext = name.slice(i + 1).toLowerCase(); + return ext === 'jpg' || ext === 'jpeg' ? 'png' : ext; + } +} diff --git a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html index b4594a106..d97a896bb 100644 --- a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html +++ b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html @@ -6,7 +6,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
+

@if (documentIdentifier) { @@ -23,85 +23,439 @@

-
- @if (documentIdentifier) { - -
-
- - {{ 'labels.inputs.Document Type' | translate }} - - @for (docType of allowedDocumentTypes; track docType) { - - {{ docType.name }} - + @if (documentIdentifier) { +
+ +
+ KYC Document Upload +

+ Upload KYC documents as per RBI guidelines. Accepted: + JPG, + PNG, + PDF — max 5MB each. +

+
+
+ + @if (loadingProofs) { +
+ Loading document types… +
+ } + + + +
+
+
+ +
+
+

+ Identity proof +

+

+ Select document type — upload requirements will update automatically +

+
+
+ + + @if (clientName) { +
+ +
+ Full name (from registration) + {{ clientName }} +
+ +
+ } + +
+ +
+ + + +
+
+ + @if (selectedIdProof) { +
+ +
+ + +
+
+ } + + @if (selectedIdProof?.info) { +
+ + {{ selectedIdProof.info }} +
+ } + + @if (selectedIdProof) { +
+
+
+ + + {{ selectedIdProof.sides === 2 ? 'Front side' : (selectedIdProof.name + ' (single copy)') }} + + + Required + +
+ +
+ + @if (selectedIdProof.sides === 2) { +
+
+ + Back side + + + Required + +
+ +
} - -
-
- - {{ 'labels.inputs.Status' | translate }} - - @for (status of statusOptions; track status) { - - {{ status.label }} - - } - - -
-
- - {{ 'labels.inputs.Document Key' | translate }} - - @if (uploadDocumentForm.controls.documentKey?.hasError('required')) { - - {{ 'labels.inputs.Document Key' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - } - -
-
- - {{ 'labels.inputs.Description' | translate }} - - -
+
+ + @if (idMergedPreview && selectedIdProof?.sides === 2) { +
+
+ + Combined preview (front + back) + + + Saved as a single PNG. Re-crop a side to update the merged file. + +
+
+ Merged ID document +
+
+ + +
+
+ } + }
- -
- - {{ 'labels.heading.Documents' | translate }} + +
+
+
+ +
+
+

+ Address proof +

+

+ Select document type — upload requirements will update automatically +

+
+
+ + + @if (clientAddress) { +
+ +
+ Permanent address (from registration) + {{ clientAddress }} +
+ +
+ } @else { +
+ + No permanent address found on file. Add one in the Address tab first. +
+ } + +
+ +
+ @for (group of addressGroups; track group.label; let last = $last) { +
+ {{ group.label }} +
+
+ @for (p of group.items; track p.id) { + + @if (isAddrProofSelected(p)) { + + } @else { + + } + {{ p.name }} + + } +
+ @if (!last) { +
+ } + } +
+
+ + @if (selectedAddrProof?.info) { +
+ + {{ selectedAddrProof.info }} +
+ } + + @if (selectedAddrProof) { +
+
+
+ + + {{ selectedAddrProof.sides === 2 ? 'Front side' : (selectedAddrProof.name + ' (single copy)') }} + + + Required + +
+ +
+ + @if (selectedAddrProof.sides === 2) { +
+
+ + Back side + + + Required + +
+ +
+ } + + @if (selectedAddrProof.requiresDate) { +
+ +
+ + +
+
+ } +
+ + @if (addrMergedPreview && selectedAddrProof?.sides === 2) { +
+
+ + Combined preview (front + back) + + + Saved as a single PNG. Re-crop a side to update the merged file. + +
+
+ Merged address document +
+
+ + +
+
+ } + } +
+ +
+ + +
+ +
+ + + @if (isIdComplete()) { } + + Identity proof + + + + @if (isAddressComplete()) { } + + Address proof + + + Fill in at least one section
-
-
- - {{ 'labels.inputs.File Name' | translate }} - - @if (uploadDocumentForm.controls.fileName.hasError('required')) { - - {{ 'labels.inputs.File Name' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - } - -
-
- -
-
- } @else { - + + } @else { +
@@ -125,24 +479,25 @@
- } - + + }
+
+ @if (!isEditMode && kycData) { + + } + +
@@ -255,6 +262,100 @@
}
+ + +
+
+
+ +
+
+

Re-KYC Completion

+

Print self-declaration, get it signed, upload & complete re-KYC.

+
+
+ + +
+
+
+ 1 +
+
+
+
Download Self Declaration Form
+
System-generated PDF with PAN, Aadhaar & KYC details
+ +
+
+ + +
+
+ 2 +
+
+
+
Upload Signed Declaration
+
Get the form signed by the client and upload the scanned copy
+ +
+
+ + +
+
+ 3 +
+
+
Complete Re-KYC
+
Updates Last Verified date and sets new Expiry Date as per RBI norms
+ +
+
+
+ + @if (reKycSuccess) { +
+ +
+ Re-KYC completed successfully +

Last Verified and Expiry Date have been updated.

+
+
+ } + @if (reKycError) { +
+ error_outline +
+ Re-KYC failed +

{{ reKycError }}

+
+
+ } +
} diff --git a/src/app/clients/clients-view/kyc-tab/kyc-tab.component.scss b/src/app/clients/clients-view/kyc-tab/kyc-tab.component.scss index bd1d4db06..74498d552 100644 --- a/src/app/clients/clients-view/kyc-tab/kyc-tab.component.scss +++ b/src/app/clients/clients-view/kyc-tab/kyc-tab.component.scss @@ -612,7 +612,7 @@ color: #1e293b; } -@media (max-width: 960px) { +@media (width <= 960px) { .kv-table__col { &--doc { flex: 0 0 120px; } &--date { display: none; } @@ -623,9 +623,281 @@ .kv-meta-item:nth-child(3n) { border-right: none; } } -@media (max-width: 640px) { +@media (width <= 640px) { .kv-table__row { flex-wrap: wrap; } .kv-table__col { &--status, &--action { flex: 0 0 50%; } } .kv-meta-grid { grid-template-columns: 1fr 1fr; } .kv-otp-bar { flex-wrap: wrap; } } + +/* ─── Header Actions ─── */ +.kyc-header-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.kyc-print-btn { + display: flex; + align-items: center; + gap: 6px; + color: #1074b9 !important; + border-color: #93c5fd !important; +} + +.m-r-6 { margin-right: 6px; } + +/* ─── Re-KYC Section ─── */ +.rekyc-section { + margin-top: 20px; + background: linear-gradient(135deg, #fafbff 0%, #f0f4ff 100%); + border: 1px solid #dbe4f0; + border-radius: 14px; + padding: 24px; +} + +.rekyc-header { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 22px; +} + +.rekyc-icon-wrap { + width: 40px; + height: 40px; + border-radius: 10px; + background: linear-gradient(135deg, #1074b9, #0c5da0); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.rekyc-icon { + font-size: 17px; + color: #fff; +} + +.rekyc-header-text { + flex: 1; +} + +.rekyc-title { + margin: 0; + font-size: 16px; + font-weight: 700; + color: #0f172a; +} + +.rekyc-desc { + margin: 3px 0 0; + font-size: 12px; + color: #64748b; + line-height: 1.4; +} + +/* ─── Timeline Layout ─── */ +.rekyc-timeline { + display: flex; + flex-direction: column; + gap: 0; + padding-left: 4px; +} + +.rekyc-tl-item { + display: flex; + gap: 16px; +} + +.rekyc-tl-dot { + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; + width: 32px; +} + +.rekyc-tl-num { + width: 28px; + height: 28px; + border-radius: 50%; + background: #1074b9; + color: #fff; + font-size: 12px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + z-index: 1; +} + +.rekyc-tl-line { + width: 2px; + flex: 1; + background: #c7d8ed; + margin: 4px 0; +} + +.rekyc-tl-item--last .rekyc-tl-dot { + .rekyc-tl-line { display: none; } +} + +.rekyc-tl-content { + flex: 1; + padding-bottom: 24px; +} + +.rekyc-tl-item--last .rekyc-tl-content { + padding-bottom: 0; +} + +.rekyc-tl-label { + font-size: 13px; + font-weight: 700; + color: #1e293b; + margin-bottom: 2px; +} + +.rekyc-tl-sub { + font-size: 11px; + color: #94a3b8; + margin-bottom: 10px; + line-height: 1.4; +} + +/* ─── Buttons ─── */ +.rekyc-btn-print { + display: inline-flex !important; + align-items: center; + gap: 7px; + font-size: 12px !important; + font-weight: 600 !important; + color: #1074b9 !important; + background: #fff !important; + border: 1px solid #bfdbfe !important; + border-radius: 8px !important; + padding: 6px 16px !important; + cursor: pointer; + transition: all 0.15s; + + &:hover { + background: #eff6ff !important; + border-color: #93c5fd !important; + } +} + +.rekyc-btn-complete { + display: inline-flex !important; + align-items: center; + gap: 7px; + font-size: 12px !important; + font-weight: 600 !important; + border-radius: 8px !important; + padding: 7px 20px !important; +} + +/* ─── Upload Zone ─── */ +.rekyc-upload-zone { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + padding: 16px 24px; + max-width: 280px; + background: #fff; + border: 2px dashed #cbd5e1; + border-radius: 10px; + cursor: pointer; + transition: all 0.2s; + text-align: center; + + &:hover { + background: #f8fafc; + border-color: #93c5fd; + } + + &.has-file { + background: #f0fdf4; + border-color: #86efac; + border-style: solid; + } +} + +.upload-zone-icon { + font-size: 28px !important; + width: 28px !important; + height: 28px !important; + color: #94a3b8; +} + +.upload-zone-text { + font-size: 12px; + font-weight: 600; + color: #475569; +} + +.upload-zone-hint { + font-size: 10px; + color: #94a3b8; +} + +.upload-zone-check { + font-size: 24px; + color: #22c55e; +} + +.upload-zone-filename { + font-size: 12px; + font-weight: 600; + color: #059669; + max-width: 230px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ─── Result Banners ─── */ +.rekyc-result { + display: flex; + align-items: flex-start; + gap: 10px; + margin-top: 18px; + padding: 12px 16px; + border-radius: 10px; + font-size: 13px; + line-height: 1.4; + + p { + margin: 2px 0 0; + font-size: 12px; + font-weight: 400; + } + + &--success { + background: #ecfdf5; + border: 1px solid #a7f3d0; + color: #065f46; + + .rekyc-result-icon { color: #059669; font-size: 20px; } + } + + &--error { + background: #fef2f2; + border: 1px solid #fecaca; + color: #991b1b; + + .rekyc-result-icon { + color: #dc2626; + font-size: 20px !important; + width: 20px !important; + height: 20px !important; + } + } +} + +@media (width <= 640px) { + .rekyc-upload-zone { max-width: 100%; } +} diff --git a/src/app/clients/clients-view/kyc-tab/kyc-tab.component.ts b/src/app/clients/clients-view/kyc-tab/kyc-tab.component.ts index a744fabcd..8daf7075a 100644 --- a/src/app/clients/clients-view/kyc-tab/kyc-tab.component.ts +++ b/src/app/clients/clients-view/kyc-tab/kyc-tab.component.ts @@ -15,6 +15,7 @@ import { catchError, of, Subject, takeUntil } from 'rxjs'; /** Custom Services */ import { ClientsService } from '../../clients.service'; import { Dates } from 'app/core/utils/dates'; +import { jsPDF } from 'jspdf'; import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; @@ -51,6 +52,7 @@ export class KycTabComponent implements OnInit, OnDestroy { clientId: string; kycId: string | null = null; kycData: any = null; + clientData: any = null; clientDateOfBirth: string | null = null; isNonIndividual = false; loading = true; @@ -58,6 +60,13 @@ export class KycTabComponent implements OnInit, OnDestroy { isEditMode = false; errorMessage: string | null = null; + /** Re-KYC flow state */ + reKycUploading = false; + reKycCompleting = false; + reKycSuccess = false; + reKycError: string | null = null; + selectedDeclarationFile: File | null = null; + verifying: { pan: boolean; aadhaar: boolean; gstin: boolean } = { pan: false, aadhaar: false, @@ -154,6 +163,7 @@ export class KycTabComponent implements OnInit, OnDestroy { /** Load client's date of birth and legal form from parent route resolved data */ loadClientDateOfBirth(): void { this.route.parent.data.subscribe((data: any) => { + this.clientData = data?.clientViewData || null; if (data?.clientViewData?.dateOfBirth) { const dob = data.clientViewData.dateOfBirth; if (Array.isArray(dob) && dob.length >= 3) { @@ -444,6 +454,231 @@ export class KycTabComponent implements OnInit, OnDestroy { } } + /** Generate Self Declaration PDF with KYC details */ + printSelfDeclaration(): void { + const client = this.clientData || {}; + const kyc = this.kycData || {}; + const clientName = client.displayName || client.fullname || ''; + const cifNumber = client.accountNo || client.cifNumber || this.clientId; + const dob = this.clientDateOfBirth ? this.formatDate(client.dateOfBirth) : '—'; + const officeName = client.officeName || ''; + const today = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'long', year: 'numeric' }); + + const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); + const pageWidth = doc.internal.pageSize.getWidth(); + const margin = 20; + const contentWidth = pageWidth - margin * 2; + let y = 20; + + // Helper + const addLine = (label: string, value: string, yPos: number): number => { + doc.setFont('helvetica', 'bold'); + doc.setFontSize(10); + doc.text(label, margin, yPos); + doc.setFont('helvetica', 'normal'); + doc.text(value || '—', margin + 55, yPos); + return yPos + 7; + }; + + const drawHr = (yPos: number): number => { + doc.setDrawColor(200, 200, 200); + doc.setLineWidth(0.3); + doc.line(margin, yPos, pageWidth - margin, yPos); + return yPos + 5; + }; + + // ── Header ── + doc.setFillColor(15, 23, 42); + doc.rect(0, 0, pageWidth, 30, 'F'); + doc.setTextColor(255, 255, 255); + doc.setFont('helvetica', 'bold'); + doc.setFontSize(16); + doc.text('Self Declaration Form', pageWidth / 2, 13, { align: 'center' }); + doc.setFontSize(9); + doc.setFont('helvetica', 'normal'); + doc.text('KYC / Re-KYC Compliance — As per RBI Master Direction on KYC', pageWidth / 2, 21, { align: 'center' }); + doc.setTextColor(0, 0, 0); + + y = 40; + + // ── Branch / Date ── + doc.setFontSize(10); + doc.setFont('helvetica', 'normal'); + doc.text(`Branch: ${officeName}`, margin, y); + doc.text(`Date: ${today}`, pageWidth - margin, y, { align: 'right' }); + y += 8; + y = drawHr(y); + + // ── Section: Customer Details ── + doc.setFont('helvetica', 'bold'); + doc.setFontSize(12); + doc.setTextColor(16, 116, 185); + doc.text('Customer Details', margin, y); + doc.setTextColor(0, 0, 0); + y += 8; + + y = addLine('Customer Name:', clientName, y); + y = addLine('CIF Number:', cifNumber, y); + y = addLine('Date of Birth:', dob, y); + if (client.mobileNo) y = addLine('Mobile:', client.mobileNo, y); + if (client.emailAddress) y = addLine('Email:', client.emailAddress, y); + y += 3; + y = drawHr(y); + + // ── Section: KYC Documents ── + doc.setFont('helvetica', 'bold'); + doc.setFontSize(12); + doc.setTextColor(16, 116, 185); + doc.text('KYC Document Details', margin, y); + doc.setTextColor(0, 0, 0); + y += 8; + + // PAN + y = addLine('PAN Number:', kyc.panNumber || '—', y); + y = addLine('PAN Status:', kyc.panVerified ? 'Verified' : 'Pending', y); + if (kyc.panVerifiedOn) y = addLine('PAN Verified On:', this.formatDate(kyc.panVerifiedOn), y); + + // Aadhaar + y = addLine('Aadhaar Number:', kyc.aadhaarNumberMasked || '—', y); + y = addLine('Aadhaar Status:', kyc.aadhaarVerified ? 'Verified' : 'Pending', y); + if (kyc.aadhaarVerifiedOn) y = addLine('Aadhaar Verified On:', this.formatDate(kyc.aadhaarVerifiedOn), y); + + // PAN-Aadhaar Link + const seedingStatus = kyc.panAadhaarSeedingStatus === 'Y' ? 'Linked' : kyc.panAadhaarSeedingStatus === 'N' ? 'Not Linked' : kyc.panAadhaarSeedingStatus === 'E' ? 'Exempt' : 'Pending'; + y = addLine('PAN-Aadhaar Link:', seedingStatus, y); + + // GSTIN (Non-Individual) + if (this.isNonIndividual && kyc.gstinNumber) { + y = addLine('GSTIN:', kyc.gstinNumber, y); + y = addLine('GSTIN Status:', kyc.gstinVerified ? 'Verified' : 'Pending', y); + } + + // Enterprise PAN / CIN + if (this.isNonIndividual && kyc.enterprisePan) { + y = addLine('Enterprise PAN:', kyc.enterprisePan, y); + } + if (this.isNonIndividual && kyc.cinNumber) { + y = addLine('CIN:', kyc.cinNumber, y); + } + + y += 3; + y = drawHr(y); + + // ── Section: KYC Summary ── + doc.setFont('helvetica', 'bold'); + doc.setFontSize(12); + doc.setTextColor(16, 116, 185); + doc.text('KYC Summary', margin, y); + doc.setTextColor(0, 0, 0); + y += 8; + + y = addLine('KYC Type:', kyc.kycType === 1 ? 'Individual' : kyc.kycType === 2 ? 'Non-Individual' : '—', y); + y = addLine('Risk Category:', kyc.riskCategory === 1 ? 'Low' : kyc.riskCategory === 2 ? 'Medium' : kyc.riskCategory === 3 ? 'High' : '—', y); + y = addLine('Initiated On:', this.formatDate(kyc.initiatedOn), y); + y = addLine('Last Verified:', this.formatDate(kyc.verifiedOn), y); + y = addLine('Expiry Date:', this.formatDate(kyc.expiryDate), y); + + y += 3; + y = drawHr(y); + + // ── Declaration Text ── + doc.setFont('helvetica', 'bold'); + doc.setFontSize(12); + doc.setTextColor(16, 116, 185); + doc.text('Declaration', margin, y); + doc.setTextColor(0, 0, 0); + y += 8; + + doc.setFont('helvetica', 'normal'); + doc.setFontSize(9.5); + const declarationText = `I, ${clientName || '_______________'}, hereby declare that the information provided above is true, correct and complete to the best of my knowledge and belief. I understand that furnishing false information is punishable under applicable laws. I authorize the Bank to verify the above details through appropriate channels as per RBI Master Direction on KYC (DBR.AML.BC.No.81/14.01.001/2015-16) and its subsequent amendments. + +I further confirm that: +1. I am not a Politically Exposed Person (PEP) / relative of a PEP (unless declared otherwise). +2. The source of funds in my account is from legitimate sources. +3. I shall promptly notify the Bank of any change in the above information. +4. I have read and understood the Bank's terms and conditions regarding KYC compliance.`; + + const lines = doc.splitTextToSize(declarationText, contentWidth); + doc.text(lines, margin, y); + y += lines.length * 4.5 + 10; + + // ── Signature section ── + y = drawHr(y); + y += 5; + + // Left: Client signature + doc.setFont('helvetica', 'normal'); + doc.setFontSize(9); + doc.text('________________________', margin, y + 15); + doc.text('Signature of Customer', margin, y + 21); + doc.text(`Name: ${clientName}`, margin, y + 27); + doc.text(`Date: _______________`, margin, y + 33); + doc.text(`Place: _______________`, margin, y + 39); + + // Right: Bank official + doc.text('________________________', pageWidth - margin - 50, y + 15); + doc.text('Authorized Bank Official', pageWidth - margin - 50, y + 21); + doc.text('Name: _______________', pageWidth - margin - 50, y + 27); + doc.text('Designation: _______________', pageWidth - margin - 50, y + 33); + doc.text('Stamp & Seal', pageWidth - margin - 50, y + 39); + + // ── Footer ── + const footerY = doc.internal.pageSize.getHeight() - 10; + doc.setFontSize(7); + doc.setTextColor(150, 150, 150); + doc.text('This is a system-generated document. Generated from CBS Nexora — KYC Module.', pageWidth / 2, footerY, { align: 'center' }); + + // Save + doc.save(`Self_Declaration_${cifNumber}_${clientName.replace(/\s/g, '_')}.pdf`); + } + + /** Handle file selection for signed declaration upload */ + onDeclarationFileSelected(event: Event): void { + const input = event.target as HTMLInputElement; + if (input.files?.length) { + this.selectedDeclarationFile = input.files[0]; + this.reKycError = null; + } + } + + /** Upload signed declaration and complete re-KYC */ + completeReKyc(): void { + if (!this.selectedDeclarationFile || !this.clientId || !this.kycId) return; + + this.reKycCompleting = true; + this.reKycError = null; + this.reKycSuccess = false; + + // Step 1: Upload the signed declaration as a client document + const formData = new FormData(); + formData.append('file', this.selectedDeclarationFile); + formData.append('name', 'KYC Self Declaration'); + formData.append('description', `Signed self-declaration form uploaded on ${new Date().toLocaleDateString('en-IN')}`); + + this.clientsService.uploadClientDocument(this.clientId, formData).subscribe({ + next: () => { + // Step 2: Call re-KYC completion API to update verified/expiry dates + this.clientsService.completeReKyc(this.clientId, this.kycId!).subscribe({ + next: () => { + this.reKycCompleting = false; + this.reKycSuccess = true; + this.selectedDeclarationFile = null; + this.loadKycData(); // Refresh to show updated dates + }, + error: (err: any) => { + this.reKycCompleting = false; + this.reKycError = err?.error?.defaultUserMessage || err?.error?.developerMessage || 'Failed to complete re-KYC. Please try again.'; + } + }); + }, + error: (err: any) => { + this.reKycCompleting = false; + this.reKycError = err?.error?.defaultUserMessage || err?.error?.developerMessage || 'Failed to upload declaration. Please try again.'; + } + }); + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/src/app/clients/clients-view/nominee-tab/nominee-tab.component.scss b/src/app/clients/clients-view/nominee-tab/nominee-tab.component.scss index c1c46b387..e05ba4652 100644 --- a/src/app/clients/clients-view/nominee-tab/nominee-tab.component.scss +++ b/src/app/clients/clients-view/nominee-tab/nominee-tab.component.scss @@ -16,7 +16,7 @@ $primary-light: #e8f0fe; &__left { display: flex; align-items: center; gap: 10px; } &__icon { color: #60a5fa; } &__title { font-size: 15px; font-weight: 600; color: #fff; margin: 0; } - &__sub { font-size: 10px; color: rgba(255,255,255,0.45); margin: 1px 0 0; } + &__sub { font-size: 10px; color: rgb(255 255 255 / 45%); margin: 1px 0 0; } } .nom-loading { display: flex; align-items: center; gap: 8px; padding: 24px 0; justify-content: center; font-size: 13px; color: #64748b; @@ -36,12 +36,14 @@ $primary-light: #e8f0fe; &__right { display: flex; align-items: center; gap: 10px; } } .nom-type-chips { display: flex; gap: 6px; flex-wrap: wrap; } + .nom-type-chip { display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px; border: 1.5px solid #e2e8f0; border-radius: 6px; background: #fff; font-size: 12px; font-weight: 500; color: #64748b; cursor: pointer; transition: all 0.15s; mat-icon { font-size: 16px; width: 16px; height: 16px; } &__count { font-size: 9px; font-weight: 700; background: #f1f5f9; color: #64748b; padding: 0 5px; border-radius: 6px; } + &.active { border-color: $primary; background: $primary-light; color: $primary; .nom-type-chip__count { background: $primary; color: #fff; } } @@ -56,6 +58,7 @@ $primary-light: #e8f0fe; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 8px; &__label { font-size: 11px; font-weight: 600; color: #64748b; white-space: nowrap; } &__track { flex: 1; height: 6px; background: #e2e8f0; border-radius: 3px; overflow: hidden; } + &__fill { height: 100%; border-radius: 3px; transition: width 0.3s; background: linear-gradient(90deg, #3b82f6, #10b981); @@ -67,12 +70,14 @@ $primary-light: #e8f0fe; /* Nominee card — compact */ .nom-card { border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 6px; overflow: hidden; } .nom-card__main { display: flex; align-items: center; gap: 10px; padding: 8px 12px; } + .nom-card__avatar { width: 32px; height: 32px; border-radius: 50%; background: #dbeafe; display: flex; align-items: center; justify-content: center; color: #1565c0; flex-shrink: 0; mat-icon { font-size: 18px; } } .nom-card__photo { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; } + .nom-card__sig { flex-shrink: 0; img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 3px; background: #fff; padding: 1px 3px; } @@ -80,6 +85,7 @@ $primary-light: #e8f0fe; .nom-card__info { flex: 1; min-width: 0; } .nom-card__name { font-weight: 600; font-size: 13px; color: #1e293b; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .nom-card__meta { font-size: 11px; color: #64748b; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .nom-card__id { font-size: 10px; font-weight: 600; color: #1d4ed8; background: #eff6ff; padding: 2px 8px; border-radius: 4px; white-space: nowrap; @@ -87,12 +93,15 @@ $primary-light: #e8f0fe; } .nom-card__share { text-align: center; min-width: 40px; flex-shrink: 0; } .nom-card__share-value { font-size: 15px; font-weight: 700; color: #1565c0; } + .nom-card__actions { display: flex; gap: 0; flex-shrink: 0; + button { width: 32px !important; height: 32px !important; line-height: 32px !important; mat-icon { font-size: 18px; } } } .nom-card__delete { color: #94a3b8 !important; &:hover { color: #ef4444 !important; } } + .nom-card__guardian { display: flex; align-items: center; gap: 5px; padding: 5px 12px; background: #fff7ed; font-size: 11px; color: #92400e; border-top: 1px solid #fde68a; @@ -122,13 +131,15 @@ $primary-light: #e8f0fe; .nom-search-wrapper { position: relative; margin-bottom: 8px; } .nom-search-field { width: 100%; } .nom-search-prefix { color: #94a3b8; } + .nom-search-results { position: absolute; top: 56px; left: 0; right: 0; z-index: 100; background: #fff; border: 1px solid #e2e8f0; border-radius: 6px; - box-shadow: 0 6px 20px rgba(0,0,0,0.1); max-height: 220px; overflow-y: auto; + box-shadow: 0 6px 20px rgb(0 0 0 / 10%); max-height: 220px; overflow-y: auto; &--inline { position: relative; top: 0; margin-top: 4px; } } .nom-search-loading { display: flex; align-items: center; gap: 6px; padding: 10px 12px; font-size: 12px; color: #64748b; } + .nom-search-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #f1f5f9; &:last-child { border-bottom: none; } &:hover { background: #f0f7ff; } @@ -149,6 +160,7 @@ $primary-light: #e8f0fe; &__info { flex: 1; display: flex; flex-direction: column; } &__name { font-size: 13px; font-weight: 600; color: #1e293b; } &__meta { font-size: 11px; color: #64748b; font-family: 'Roboto Mono', monospace; } + &__sig { flex-shrink: 0; img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 3px; background: #fff; padding: 1px 3px; } @@ -174,10 +186,11 @@ $primary-light: #e8f0fe; /* Guardian */ .nom-guardian-section { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; } .nom-guardian-label { display: flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; color: #92400e; mat-icon { font-size: 16px; } } + .nom-guardian-selected { display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 500; color: #1e293b; button { width: 24px !important; height: 24px !important; line-height: 24px !important; mat-icon { font-size: 14px; } } } /* Zoom */ -.zoomable { cursor: zoom-in; transition: transform 0.15s; &:hover { transform: scale(1.08); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } } +.zoomable { cursor: zoom-in; transition: transform 0.15s; &:hover { transform: scale(1.08); box-shadow: 0 4px 12px rgb(0 0 0 / 15%); } } ::ng-deep .image-zoom-panel { .mat-mdc-dialog-container { padding: 0 !important; border-radius: 8px !important; overflow: hidden; } } diff --git a/src/app/clients/clients-view/personal-data-tab/personal-data-tab.component.scss b/src/app/clients/clients-view/personal-data-tab/personal-data-tab.component.scss index abd46cba7..0bcbdec8c 100644 --- a/src/app/clients/clients-view/personal-data-tab/personal-data-tab.component.scss +++ b/src/app/clients/clients-view/personal-data-tab/personal-data-tab.component.scss @@ -83,15 +83,15 @@ $teal: #00897b; grid-template-columns: repeat(4, 1fr); gap: 0; - @media (max-width: 1024px) { + @media (width <= 1024px) { grid-template-columns: repeat(3, 1fr); } - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: repeat(2, 1fr); } - @media (max-width: 480px) { + @media (width <= 480px) { grid-template-columns: 1fr; } } @@ -192,7 +192,7 @@ $teal: #00897b; } &.monospace { - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; font-size: 13px; letter-spacing: 0.3px; } diff --git a/src/app/clients/clients.component.html b/src/app/clients/clients.component.html index 164850b67..c5227e293 100644 --- a/src/app/clients/clients.component.html +++ b/src/app/clients/clients.component.html @@ -9,28 +9,14 @@
-
-

-
- {{ 'labels.menus.Clients' | translate }} -

- {{ totalRows }} records -
- -
- - - - - @if (existsClientsToFilter) { - - {{ 'labels.inputs.Show Closed Accounts' | translate }} - - } +
+
+

+
+ {{ 'labels.menus.Clients' | translate }} +

+ {{ totalRows }} records +
+ +
+ + + + + + + All Status + Active + Pending + Closed + + + + + + All Forms + Individual + Non-Individual + + + + + + All CKYC + CKYC Done + CKYC Pending + + + +
@@ -69,6 +91,9 @@ {{ row.legalForm?.id === 2 ? 'business' : 'person' }} {{ hideClientData ? maskName(row.displayName) : row.displayName }} + @if (row._ckycDone) { + verified + } @@ -86,6 +111,18 @@ {{ row.cifNumber }} + + + CKYC Number + + @if (row._ckycNumber) { + {{ row._ckycNumber }} + } @else { + — + } + + + {{ 'labels.inputs.Legal Form' | translate }} diff --git a/src/app/clients/clients.component.ts b/src/app/clients/clients.component.ts index 6eab7aadf..65c0993a6 100644 --- a/src/app/clients/clients.component.ts +++ b/src/app/clients/clients.component.ts @@ -8,7 +8,6 @@ /** Angular Imports. */ import { Component, OnInit, ViewChild, inject } from '@angular/core'; -import { MatCheckbox } from '@angular/material/checkbox'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { MatSort, Sort, MatSortHeader } from '@angular/material/sort'; import { @@ -46,7 +45,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; styleUrls: ['./clients.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, - MatCheckbox, FaIconComponent, MatProgressBar, MatTable, @@ -88,12 +86,11 @@ export class ClientsComponent implements OnInit { .join(' '); } - @ViewChild('showClosedAccounts') showClosedAccounts: MatCheckbox; - displayedColumns = [ 'displayName', 'accountNumber', 'cifNumber', + 'ckycNumber', 'legalForm', 'status', 'profileCompletion', @@ -115,6 +112,12 @@ export class ClientsComponent implements OnInit { sortAttribute = ''; sortDirection = ''; + statusFilter = ''; + legalFormFilter = ''; + ckycFilter = ''; + + private allRows: any[] = []; + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -139,14 +142,16 @@ export class ClientsComponent implements OnInit { .searchByText(this.filterText, this.currentPage, this.pageSize, this.sortAttribute, this.sortDirection) .subscribe( (data: any) => { - this.dataSource.data = data.content; + this.allRows = data.content || []; this.totalRows = data.totalElements; this.existsClientsToFilter = data.numberOfElements > 0; this.notExistsClientsToFilter = !this.existsClientsToFilter; this.isLoading = false; + this.applyClientFilters(); + // Fetch KYC & address status for visible rows - this.loadProfileStatus(data.content); + this.loadProfileStatus(this.allRows); }, (error: any) => { this.isLoading = false; @@ -167,7 +172,7 @@ export class ClientsComponent implements OnInit { ).subscribe((data: any) => { if (data?.legalForm) { clients[i].legalForm = data.legalForm; - this.dataSource.data = [...clients]; + this.applyClientFilters(); } }); @@ -176,7 +181,9 @@ export class ClientsComponent implements OnInit { ).subscribe((kyc: any) => { if (kyc) { clients[i]._kycDone = kyc.kycStatus === 300; - this.dataSource.data = [...clients]; + clients[i]._ckycDone = !!kyc.ckycNumber; + clients[i]._ckycNumber = kyc.ckycNumber || ''; + this.applyClientFilters(); } }); @@ -185,12 +192,38 @@ export class ClientsComponent implements OnInit { ).subscribe((addr: any) => { if (addr && Array.isArray(addr) && addr.length > 0) { clients[i]._addressDone = true; - this.dataSource.data = [...clients]; + this.applyClientFilters(); } }); }); } + applyClientFilters() { + const status = this.statusFilter; + const legal = this.legalFormFilter; + const ckyc = this.ckycFilter; + + const filtered = (this.allRows || []).filter((row) => { + const code = (row?.status?.code || '').toLowerCase(); + + if (status && !code.includes(status)) { + return false; + } + if (legal && String(row?.legalForm?.id ?? '') !== legal) { + return false; + } + if (ckyc === 'done' && !row._ckycDone) { + return false; + } + if (ckyc === 'pending' && row._ckycDone) { + return false; + } + return true; + }); + + this.dataSource.data = filtered; + } + pageChanged(event: PageEvent) { this.pageSize = event.pageSize; this.currentPage = event.pageIndex; diff --git a/src/app/clients/clients.service.ts b/src/app/clients/clients.service.ts index 75299705d..8937f50ae 100644 --- a/src/app/clients/clients.service.ts +++ b/src/app/clients/clients.service.ts @@ -320,6 +320,80 @@ export class ClientsService { return this.http.get(`/clients/${clientId}/identifiers`); } + getKycProofTypes(type?: 'id' | 'address'): Observable { + const options = type ? { params: new HttpParams().set('type', type) } : {}; + return this.http.get(`/kyc/proof-types`, options); + } + + /** + * Phase 1 – Record Master: fetch clients missing CKYC registration + * or artefacts. Backed by GET /ckyc/record-master. + */ + getCkycRecordMaster(filters: { + officeId?: number | string; + productId?: number | string; + fromDate?: string; + toDate?: string; + missing?: 'photo' | 'signature' | 'id' | 'address' | 'any'; + limit?: number; + offset?: number; + } = {}): Observable { + let params = new HttpParams(); + Object.entries(filters).forEach(([key, value]) => { + if (value !== null && value !== undefined && value !== '') { + params = params.set(key, String(value)); + } + }); + return this.http.get(`/ckyc/record-master`, { params }); + } + + /** + * Phase 2 – Auto ZIP Creation: bundle selected clients into a CERSAI + * ZIP on the server. Backed by POST /ckyc/batches. + */ + createCkycBatch(body: { clientIds: (number | string)[]; remarks?: string }): Observable { + return this.http.post(`/ckyc/batches`, body); + } + + /** + * Phase 2/3 – List CKYC batches. Supports filters and an optional + * groupByDate=true to bucket results by created_on for the UI. + */ + getCkycBatches(filters: { + fromDate?: string; + toDate?: string; + uploadedToCersai?: boolean; + groupByDate?: boolean; + } = {}): Observable { + let params = new HttpParams(); + Object.entries(filters).forEach(([key, value]) => { + if (value !== null && value !== undefined && value !== '') { + params = params.set(key, String(value)); + } + }); + return this.http.get(`/ckyc/batches`, { params }); + } + + /** Fetch a single CKYC batch with parsed manifest. */ + getCkycBatchById(id: number | string): Observable { + return this.http.get(`/ckyc/batches/${id}`); + } + + /** Download the ZIP attached to a batch (via generic document endpoint). */ + downloadCkycBatchZip(parentEntityId: number | string, documentId: number | string): Observable { + return this.http.get(`/ckyc_batch/${parentEntityId}/documents/${documentId}/attachment`, { + responseType: 'blob' + }); + } + + /** + * Phase 3 – SFTP upload to CERSAI. Uploads the batch ZIP over the + * SFTP channel and returns the ACK + per-client CKYC numbers. + */ + uploadCkycBatchToSftp(batchId: number | string): Observable { + return this.http.post(`/ckyc/batches/${batchId}/sftp-upload`, {}); + } + getClientIdentifierTemplate(clientId: string) { return this.http.get(`/clients/${clientId}/identifiers/template`); } @@ -653,6 +727,29 @@ export class ClientsService { return this.http.post(`/clients/${clientId}/kyc/${kycId}?command=verify-gstin`, {}); } + /** + * Verifies DIN (Director Identification Number) for an authorized signatory. + * Sends signatory clientId, DIN number and the non-individual entity name. + * Backend saves DIN & verification status in KYC table and returns company list. + * @param clientId Signatory's Client ID + * @param kycId KYC record ID + * @param dinNumber DIN number to verify + * @param entityName Non-individual entity name (for company match) + */ + verifyDIN(clientId: string, kycId: string, dinNumber: string, entityName: string): Observable { + return this.http.post(`/clients/${clientId}/kyc/${kycId}?command=verify-din`, { dinNumber, entityName }); + } + + /** + * Completes Re-KYC after uploading signed self-declaration form. + * Updates Last Verified date to today and sets new Expiry Date (today + 10 years for low risk). + * @param clientId Client ID + * @param kycId KYC record ID + */ + completeReKyc(clientId: string, kycId: string): Observable { + return this.http.post(`/clients/${clientId}/kyc/${kycId}?command=complete-rekyc`, {}); + } + // ═══ Entity Classification APIs ═══ getEntityClassifications(clientId: string): Observable { diff --git a/src/app/clients/create-client/create-client.component.scss b/src/app/clients/create-client/create-client.component.scss index e4bbc0965..dfdfc354c 100644 --- a/src/app/clients/create-client/create-client.component.scss +++ b/src/app/clients/create-client/create-client.component.scss @@ -48,11 +48,11 @@ .client-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -96,11 +96,11 @@ align-items: center; gap: 8px; padding: 32px 16px; - color: rgba(0, 0, 0, 0.54); + color: rgb(0 0 0 / 54%); font-size: 14px; mat-icon { - color: rgba(0, 0, 0, 0.38); + color: rgb(0 0 0 / 38%); } } @@ -110,7 +110,7 @@ align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } .action-bar-right { @@ -119,7 +119,7 @@ } .cancel-btn { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } /* ─── Read-only tab state ─── */ @@ -196,8 +196,9 @@ ::ng-deep .mdc-linear-progress__bar-inner { border-color: #fff !important; } + ::ng-deep .mdc-linear-progress__buffer-bar { - background-color: rgba(255, 255, 255, 0.3) !important; + background-color: rgb(255 255 255 / 30%) !important; } } diff --git a/src/app/core/shell/sidenav/sidenav.component.html b/src/app/core/shell/sidenav/sidenav.component.html index 6fbe65754..d767776ae 100644 --- a/src/app/core/shell/sidenav/sidenav.component.html +++ b/src/app/core/shell/sidenav/sidenav.component.html @@ -65,6 +65,18 @@ {{ 'labels.menus.CKYC' | translate }} + + + + + KYC Management +
- -
+ +
-
-
Portfolio Quality
+
+
Credit Information Companies
+
Data Quality Report (DQR) — as per RBI Guidelines
-
Decay Analysis
-
Moderate
+ +
+ TransUnion CIBIL + Credit Information Bureau India Limited +
+
98.2%
-
TSWM
-
Active
+ +
+ Experian + Experian Credit Information Company of India +
+
97.5%
+
+
+ +
+ Equifax + Equifax Credit Information Services Pvt. Ltd. +
+
94.8%
+
+
+ +
+ CRIF High Mark + CRIF High Mark Credit Information Services +
+
96.1%
diff --git a/src/app/home/dashboard/dashboard.component.scss b/src/app/home/dashboard/dashboard.component.scss index 85c44f4e7..ca3372b38 100644 --- a/src/app/home/dashboard/dashboard.component.scss +++ b/src/app/home/dashboard/dashboard.component.scss @@ -45,6 +45,7 @@ $purple: #7c3aed; .search-row { width: 100%; margin: 0; + mat-card-content { width: 100%; } @@ -63,7 +64,7 @@ $purple: #7c3aed; display: flex; align-items: center; gap: 8px; - margin: 6px 0 6px; + margin: 6px 0; padding: 5px 12px; border-radius: 6px; font-size: 14px; @@ -78,18 +79,22 @@ $purple: #7c3aed; background: #e3f2fd; color: #0d47a1; } + &.financial { background: #e8f5e9; color: #1b5e20; } + &.deposits { background: #f3e5f5; color: #6a1b9a; } + &.risk { background: #fce4ec; color: #b71c1c; } + &.alerts { background: #fff3e0; color: #e65100; @@ -105,12 +110,15 @@ $purple: #7c3aed; .four-col { grid-template-columns: repeat(4, 1fr); } + .five-col { grid-template-columns: repeat(5, 1fr); } + .three-col { grid-template-columns: repeat(3, 1fr); } + .two-col { grid-template-columns: repeat(2, 1fr); } @@ -153,15 +161,19 @@ $purple: #7c3aed; &.blue { background: $blue; } + &.green { background: $green; } + &.orange { background: $orange; } + &.red { background: $red; } + &.purple { background: $purple; } @@ -194,10 +206,12 @@ $purple: #7c3aed; background: #ecfdf5; color: #065f46; } + &.warning { background: #fffbeb; color: #92400e; } + &.info { background: #eff6ff; color: #1e40af; @@ -268,6 +282,7 @@ $purple: #7c3aed; font-size: 12px; color: #94a3b8; } + .metric-value { font-size: 14px; font-weight: 700; @@ -339,6 +354,7 @@ $purple: #7c3aed; background: #ecfdf5; color: #065f46; } + &.exceed { background: #fef2f2; color: #991b1b; @@ -365,15 +381,19 @@ $purple: #7c3aed; &.blue { background: $blue; } + &.green { background: $green; } + &.purple { background: $purple; } + &.orange { background: $orange; } + &.red { background: $red; } @@ -441,9 +461,11 @@ $purple: #7c3aed; .compact-icon { font-size: 14px; + &.purple-text { color: $purple; } + &.orange-text { color: $orange; } @@ -469,13 +491,16 @@ $purple: #7c3aed; color: #64748b; margin-bottom: 2px; } + .da-value { font-size: 20px; font-weight: 800; margin-bottom: 6px; + &.deposit { color: $blue; } + &.advance { color: $purple; } @@ -497,6 +522,7 @@ $purple: #7c3aed; &.deposit-fill { background: linear-gradient(90deg, $blue, #42a5f5); } + &.advance-fill { background: linear-gradient(90deg, $purple, #a78bfa); } @@ -532,9 +558,11 @@ $purple: #7c3aed; .psl-bar { height: 100%; border-radius: 5px; + &.weaker { background: $orange; } + &.msme { background: $green; } @@ -548,38 +576,94 @@ $purple: #7c3aed; text-align: right; } -/* ─── Portfolio Quality ─── */ +/* ─── Credit Information Companies — DQR ─── */ +.cic-card { + .card-top { + margin-bottom: 0; + } +} + +.cic-subtitle { + font-size: 11px; + font-weight: 600; + color: #94a3b8; + letter-spacing: 0.3px; + margin: 2px 0 10px; +} + .quality-grid { display: flex; flex-direction: column; - gap: 14px; - margin-top: 8px; + gap: 0; + margin-top: 0; } .quality-item { display: flex; - justify-content: space-between; align-items: center; + gap: 10px; + padding: 10px 0; + border-bottom: 1px solid #f1f5f9; + + .quality-label { + flex: 1; + min-width: 0; + } + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } +} + +.cic-logo { + width: 34px; + height: 34px; + border-radius: 7px; + flex-shrink: 0; + object-fit: contain; } .quality-label { - font-size: 13px; - color: #475569; + display: flex; + flex-direction: column; + gap: 1px; } -.quality-badge { - font-size: 12px; - font-weight: 700; +.cic-name { + font-size: 13px; + font-weight: 600; + color: #1e293b; +} + +.cic-full { + font-size: 10px; + color: #94a3b8; + line-height: 1.3; +} + +.dqr-percent { + font-size: 14px; + font-weight: 800; padding: 4px 12px; border-radius: 20px; + flex-shrink: 0; + font-family: 'JetBrains Mono', 'Fira Code', monospace; + letter-spacing: 0.3px; + + &.good { + background: #ecfdf5; + color: #065f46; + } &.moderate { background: #fffbeb; color: #92400e; } - &.active { - background: #ecfdf5; - color: #065f46; + + &.low { + background: #fef2f2; + color: #991b1b; } } @@ -629,10 +713,12 @@ $purple: #7c3aed; &.success { color: #065f46; } + &.warning { color: #b45309; } } + .npa-note { font-size: 11px; color: #94a3b8; @@ -664,9 +750,11 @@ $purple: #7c3aed; &.green { background: $green; } + &.yellow { background: $orange; } + &.red { background: $red; } @@ -715,9 +803,11 @@ $purple: #7c3aed; .four-col { grid-template-columns: repeat(2, 1fr); } + .five-col { grid-template-columns: repeat(3, 1fr); } + .three-col { grid-template-columns: repeat(2, 1fr); } @@ -733,6 +823,7 @@ $purple: #7c3aed; .card-big-value { font-size: 26px; } + .mini-value { font-size: 18px; } diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index c983ca667..08424d073 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -31,7 +31,7 @@ $accent: #0ea5e9; &::after { content: ''; position: absolute; - top: 0; left: 0; right: 0; bottom: 0; + inset: 0; background: repeating-linear-gradient(108deg, transparent 0, transparent 80px, rgb(255 255 255 / 2.5%) 80px, rgb(255 255 255 / 2.5%) 160px); pointer-events: none; } @@ -47,7 +47,7 @@ $accent: #0ea5e9; margin: 0; font-size: 20px; font-weight: 400; - color: rgba(255, 255, 255, 0.9); + color: rgb(255 255 255 / 90%); strong { font-weight: 700; @@ -59,7 +59,7 @@ $accent: #0ea5e9; .welcome-sub { margin: 4px 0 0; font-size: 12px; - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); } .welcome-right { @@ -72,21 +72,21 @@ $accent: #0ea5e9; display: flex; align-items: center; gap: 8px; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.15); + background: rgb(255 255 255 / 10%); + border: 1px solid rgb(255 255 255 / 15%); border-radius: 6px; padding: 6px 14px; min-width: 280px; transition: background 0.15s; &:focus-within { - background: rgba(255, 255, 255, 0.18); - border-color: rgba(255, 255, 255, 0.3); + background: rgb(255 255 255 / 18%); + border-color: rgb(255 255 255 / 30%); } } .search-icon { - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); font-size: 13px; } @@ -99,7 +99,7 @@ $accent: #0ea5e9; width: 100%; &::placeholder { - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); } } @@ -115,16 +115,16 @@ $accent: #0ea5e9; display: flex; align-items: center; gap: 10px; - padding: 14px 14px; + padding: 14px; background: #fff; - border: 1px solid rgba(0, 0, 0, 0.06); + border: 1px solid rgb(0 0 0 / 6%); border-radius: 8px; text-decoration: none; transition: box-shadow 0.15s, transform 0.15s; cursor: pointer; &:hover { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 16px rgb(0 0 0 / 8%); transform: translateY(-1px); } } @@ -175,7 +175,7 @@ $accent: #0ea5e9; .info-card { background: #fff; - border: 1px solid rgba(0, 0, 0, 0.06); + border: 1px solid rgb(0 0 0 / 6%); border-radius: 8px; overflow: hidden; } @@ -186,7 +186,7 @@ $accent: #0ea5e9; gap: 8px; padding: 10px 16px; background: #fafafa; - border-bottom: 1px solid rgba(0, 0, 0, 0.04); + border-bottom: 1px solid rgb(0 0 0 / 4%); font-size: 13px; font-weight: 600; color: #333; @@ -208,7 +208,7 @@ $accent: #0ea5e9; display: flex; flex-direction: column; padding: 8px 16px; - border-bottom: 1px solid rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgb(0 0 0 / 3%); &:nth-last-child(-n+2) { border-bottom: none; @@ -239,7 +239,7 @@ $accent: #0ea5e9; text-decoration: none; padding: 7px 16px; transition: background 0.1s; - border-bottom: 1px solid rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgb(0 0 0 / 3%); &:hover { background: #f0f9ff; @@ -256,7 +256,7 @@ $accent: #0ea5e9; :host-context(.dark-theme) { .qa-card, .info-card { background: #1e1e2e; - border-color: rgba(255, 255, 255, 0.06); + border-color: rgb(255 255 255 / 6%); } .qa-title { color: #e0e0e0; } @@ -267,7 +267,7 @@ $accent: #0ea5e9; .quick-links a { color: #7dd3fc; - &:hover { background: rgba(255, 255, 255, 0.04); } + &:hover { background: rgb(255 255 255 / 4%); } } .search-input { @@ -276,13 +276,13 @@ $accent: #0ea5e9; } /* ── Responsive ── */ -@media (max-width: 1024px) { +@media (width <= 1024px) { .quick-actions { grid-template-columns: repeat(3, 1fr); } } -@media (max-width: 768px) { +@media (width <= 768px) { .welcome-banner { flex-direction: column; align-items: stretch; @@ -302,7 +302,7 @@ $accent: #0ea5e9; } } -@media (max-width: 480px) { +@media (width <= 480px) { .quick-actions { grid-template-columns: 1fr; } diff --git a/src/app/kyc-management/kyc-management-routing.module.ts b/src/app/kyc-management/kyc-management-routing.module.ts new file mode 100644 index 000000000..d07284047 --- /dev/null +++ b/src/app/kyc-management/kyc-management-routing.module.ts @@ -0,0 +1,34 @@ +/** + * 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 { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { Route } from '../core/route/route.service'; +import { KycManagementComponent } from './kyc-management.component'; + +const routes: Routes = [ + Route.withShell([ + { + path: 'kyc-management', + data: { title: 'KYC Management', breadcrumb: 'KYC Management' }, + children: [ + { + path: '', + component: KycManagementComponent + } + ] + } + ]) +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class KycManagementRoutingModule {} diff --git a/src/app/kyc-management/kyc-management.component.html b/src/app/kyc-management/kyc-management.component.html new file mode 100644 index 000000000..613116623 --- /dev/null +++ b/src/app/kyc-management/kyc-management.component.html @@ -0,0 +1,769 @@ + + +
+ +
+ + + + +
+ +
+ + @if (activeSection === 'alerts') { +
+
+ KYC Alert Dashboard +
+ + +
+
+ +
+
+
47
+
Expiring in 30 days
+
▲ 12 this month
+
+
+
23
+
Debit Frozen
+
Action required
+
+
+
8
+
Inoperative
+
Escalating
+
+
+
3
+
Dormant
+
RBI reportable
+
+
+
18
+
Notices Today
+
SMS + Email
+
+
+ +
+ + + + + +
+ +
+
+
+
Accounts Requiring Action
+ Showing {{ alertRows.length }} of 81 · Click row for client details +
+ + + + + + + + + @for (row of alertRows; track row.id) { + + + + + + + + + + + + } + +
CustomerRiskExpiry DateDays LeftNoticesStatePending ActionNext Stage
+
{{ row.name }}
+
{{ row.account }}
+
{{ row.riskLabel.split(' ')[0] }}{{ row.expiry }} +
+ {{ row.daysLeft > 0 ? row.daysLeft : '—' }} +
+
+
+
+
{{ row.noticesSent }}/3{{ stateLabel(row.state) }}{{ row.noticePending }}{{ row.nextStage }} + +
+
+
+ } + + + @if (activeSection === 'notif') { +
+
+ Notification Scheduler +
+ + +
+
+ +
+
+
+
+
3-Notice Dispatch Timeline
+ Expiry: 30 Apr 2027 · Window: 31 Mar – 30 Apr +
+
+
+
+
1
+
+
Notice 1 — Alert window opens
+
31 March 2027 · Auto-scheduled · ✓ Delivered
+
+ SMS + Email +
+
+
+
+
2
+
+
Notice 2 — Follow-up
+
10 April 2027 · +10 days · ⏳ Pending
+
+ SMS + Email + IVR Call + Reg. Post +
+
+
+
+
3
+
+
Notice 3 — Final notice
+
20 April 2027 · +20 days · ✗ Not dispatched
+
+ SMS + Email + IVR + Reg. Post + Branch Visit +
+
+ + +
+
+
+
+
!
+
+
Debit Freeze — if no action
+
30 April 2027 · Auto-triggered on expiry
+
+ Auto-triggered + Credits allowed + Debits blocked +
+
+
+
+
!
+
+
Inoperative — +60 days
+
30 June 2027 · No compliance
+
All txns blocked
+
+
+
+
!
+
+
Dormant — +150 days
+
30 September 2027
+
+ Dormant + RBI reporting +
+
+
+
+
+
+ +
+
+
+
+
Channels — Notice 3
+
+
+
+
📱
+
SMS
+
Registered mobile
+
+
+
📧
+
Email
+
Registered email
+
+
+
📞
+
IVR Call
+
Outbound IVR
+
+
+
📮
+
Reg. Post
+
India Post
+
+
+
🏦
+
Branch Visit
+
Manual
+
+
+
📹
+
Video KYC
+
V-CIP app
+
+
+
+ +
+
+
+
India Post Notice
+
+
+
Dispatch Date
10 April 2027
+
Reg. Post Docket No.
RP-BPL-20270410-443
+
Status
In Transit
+ +
+
+ +
+
+
+
Branch Visit
+
+
+
Scheduled Date
+ +
Officer
+ + +
+
+
+
+
+ } + + + @if (activeSection === 'audit') { +
+
+ Audit Inventory +
+ + +
+
+ +
+ + + + + +
+ +
+
+
+
KYC Notice Log
+ Click row for client details +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#Account / CustomerNoticeChannelDispatchedReference / DocketDeliveryClient ActionOfficer
001
Rajesh K. Sharma
ACC-BPL-2024-00847
Notice 1SMS + Email31 Mar 2027MSG-2027-0331-001✓ DeliveredAwaitedScheduler
002
Rajesh K. Sharma
ACC-BPL-2024-00847
Notice 2Reg. Post + IVR10 Apr 2027RP-BPL-20270410-443In TransitAwaitedScheduler
003
Priya Malhotra
ACC-BPL-2021-00231
Notice 1SMS + Email10 Mar 2027MSG-2027-0310-003✓ DeliveredNo ResponseScheduler
004
Priya Malhotra
ACC-BPL-2021-00231
Notice 2Reg. Post20 Mar 2027RP-BPL-20270320-091✓ DeliveredNo ResponseScheduler
005
Priya Malhotra
ACC-BPL-2021-00231
Notice 3Branch Visit30 Mar 2027VIS-BPL-20270330-012✓ DoneRefusedA. Mishra
006
Suresh Patel
ACC-BPL-2019-00112
Notice 3Video KYC5 Feb 2027VKYC-2027-0205-007No ResponseNo ActionSystem
+
+ Showing 6 of 12 +
+ + +
+
+
+
+ } + + + @if (activeSection === 'status') { +
+
+ Account Status Management + +
+ +
+
+
Active
1,847
KYC valid
+
+
+
Alert Window
47
Expiring < 30d
+
+
+
Debit Freeze
23
Debits blocked
+
+
+
Inoperative
8
All txns blocked
+
+
+
Dormant
3
RBI reportable
+
+
+ +
+
+
+
+
Auto-Transition Rules
+
+
+
+
Active → Debit FreezeEnabled
+
KYC expires + 3 notices sent + no response → debits blocked, credits allowed
+
+ Window: 31 Mar – 30 Apr + 3/3 notices +
+
+
+
Debit Freeze → InoperativeEnabled
+
+60 days from expiry, still unresolved → all transactions blocked
+
Apr 30 – Jun 30
+
+
+
Inoperative → DormantEnabled
+
+150 days from expiry → dormant, RBI Dormant Account reporting triggered
+
+ Jun 30 – Sep 30 + RBI notification +
+
+
+
+ +
+
+
+
Manual Override
+ Maker-Checker +
+
+
Account
+ +
Current State
+
Debit Freeze
+
Override Action
+ +
Reason
+ +
+ + +
+
+
⚠ Maker-Checker Required
+
All manual changes require second officer approval and are logged in audit inventory.
+
+
+
+
+ +
+
+
+
Recent State Transitions
+ Click row for client details +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AccountFromToTriggerDateByAction
Priya Malhotra
ACC-BPL-2021-00231
ActiveDebit FreezeAuto — KYC Expired10 Apr 2027Scheduler
Suresh Patel
ACC-BPL-2019-00112
Debit FreezeInoperativeAuto — 60d elapsed15 May 2027Scheduler
Kavitha Nair
ACC-BPL-2018-00088
InoperativeDormantAuto — 150d elapsed1 Jun 2027Scheduler
+
+
+ } +
+ + + @if (modalOpen && current) { +
+
+
+
{{ current.av }}
+
+
{{ current.name }}
+
{{ current.meta }}
+
+ {{ stateLabel(current.state) }} + {{ current.riskLabel }} +
✕
+
+
+
+ + + + + +
+ + + @if (modalTab === 'overview') { +
Compliance Journey
+
+ @for (step of getStepperState(current); track step.label) { +
+
{{ stepIcon(step.state) }}
+
{{ step.label }}
+
+ } +
+ +
+
⚡
+
+
{{ stateLabel(current.state) }} — {{ current.daysLeft }} days to expiry
+
Next pending: {{ current.noticePending }}. Next stage: {{ current.nextStage }}.
+
+
+ +
+
{{ current.noticesSent }}
Notices Sent
+
{{ noticesPending(current) }}
Notices Pending
+
{{ current.actionTaken.length }}
Actions Taken
+
{{ current.pendingActions.length }}
Pending Actions
+
+ +
+
+
KYC Information
+
+
PAN
{{ current.kyc.pan }}
+
PAN Status
{{ current.kyc.panStatus }}
+
Aadhaar
{{ current.kyc.aadhaar }}
+
Aadhaar Status
{{ current.kyc.aadhaarStatus }}
+
KYC Validity
{{ current.kyc.validity }}
+
Expiry Date
{{ current.expiry }}
+
+
+
+
Contact Details
+
+
Mobile
{{ current.contact.mobile }}
+
Email
{{ current.contact.email }}
+
Address
{{ current.contact.address }}
+
Branch
{{ current.contact.branch }}
+
+
+
+ } + + + @if (modalTab === 'timeline') { +
Notice & Escalation Timeline
+
+ @for (item of current.actionTaken; track $index) { +
+
✓
+
+
{{ item }}
+
Completed
+
+
+ } + @for (item of current.pendingActions; track $index) { +
+
!
+
+
{{ item }}
+
Pending action
+
+
+ } +
+ } + + + @if (modalTab === 'notices') { +
All Notices Dispatched
+ + + + + + @for (n of current.notices; track n.ref; let i = $index) { + + + + + + + + + + } + @if (noticesPending(current) > 0) { + + } + +
#NoticeChannelDateReferenceDeliveryResponse
{{ i + 1 }}{{ n.no }}{{ n.channel }}{{ n.date }}{{ n.ref }}{{ n.delivery }}{{ n.response }}
⏳ {{ noticesPending(current) }} notice(s) still pending dispatch
+ } + + + @if (modalTab === 'evidence') { + @if (!current.evidence) { +
+
📄
+
No evidence collected yet for this client.
+
Evidence is captured during branch visits or video KYC sessions.
+
+ } @else { +
{{ current.evidence.type === 'visit' ? 'Branch Visit Evidence' : 'Video KYC Session Log' }}
+
+
Date
{{ current.evidence.date }}
+
Officer / Agent
{{ current.evidence.officer }}
+ @if (current.evidence.type === 'visit') { +
GPS Coordinates
{{ current.evidence.gps }}
+
Location
{{ current.evidence.location }}
+ } +
Reference / Docket
{{ current.evidence.docket || current.evidence.ref }}
+
Outcome
No Compliance
+
+ @if (current.evidence.type === 'visit') { +
Map Placeholder
+
{{ current.evidence.gps }}
{{ current.evidence.location }}
+
Photo Evidence
+
Visit photo uploaded · Tap to view
+ } +
Officer Notes
+
{{ current.evidence.outcome }}
+
+ + +
+ } + } + + + @if (modalTab === 'action') { +
Available Actions
+
+ @for (action of current.pendingActions; track action) { +
+
+
{{ action }}
+
Initiate via available channels
+
+ +
+ } +
+ } +
+
+
+ } + + +
{{ toastMsg }}
+
diff --git a/src/app/kyc-management/kyc-management.component.scss b/src/app/kyc-management/kyc-management.component.scss new file mode 100644 index 000000000..fa1a5e711 --- /dev/null +++ b/src/app/kyc-management/kyc-management.component.scss @@ -0,0 +1,1105 @@ +/** + * 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/. + */ + +:host { + --km-navy: #0b1e3d; + --km-navy-mid: #163260; + --km-teal: #0e7e6b; + --km-teal-l: #e0f4f0; + --km-teal-m: #1aaf96; + --km-amber: #d97706; + --km-amber-l: #fef3c7; + --km-red: #c0392b; + --km-red-l: #fdecea; + --km-orange: #ea580c; + --km-orange-l: #fff0e6; + --km-green: #15803d; + --km-green-l: #dcfce7; + --km-blue: #1d4ed8; + --km-blue-l: #eff6ff; + --km-g50: #f8fafc; + --km-g100: #f1f5f9; + --km-g200: #e2e8f0; + --km-g300: #cbd5e1; + --km-g400: #94a3b8; + --km-g500: #64748b; + --km-g600: #475569; + --km-g700: #334155; + --km-g800: #1e293b; + --km-g900: #0f172a; + + display: block; + font-family: 'DM Sans', system-ui, -apple-system, sans-serif; + background: var(--km-g50); + color: var(--km-g800); + font-size: 14px; + line-height: 1.5; +} + +.km-root { + position: relative; +} + +/* Inner tabs */ +.km-inner-nav { + background: var(--km-g100); + border-bottom: 1px solid var(--km-g200); + padding: 0 24px; + display: flex; + gap: 0; + overflow-x: auto; +} + +.km-itab { + padding: 11px 16px; + font-size: 12px; + font-weight: 500; + color: var(--km-g500); + border: none; + background: none; + cursor: pointer; + border-bottom: 2px solid transparent; + white-space: nowrap; + display: flex; + align-items: center; + gap: 6px; + transition: all 0.15s; + font-family: inherit; + + &:hover { + color: var(--km-g800); + } + + &.active { + color: var(--km-teal); + border-bottom-color: var(--km-teal); + font-weight: 600; + background: #fff; + } +} + +.km-cnt { + background: var(--km-teal-l); + color: var(--km-teal); + border-radius: 10px; + padding: 1px 7px; + font-size: 10px; + font-weight: 700; + + &--red { + background: var(--km-red-l); + color: var(--km-red); + } + + &--amb { + background: var(--km-amber-l); + color: var(--km-amber); + } +} + +.km-main { + padding: 20px 24px; + max-width: 1400px; +} + +.km-page-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.km-page-title { + font-size: 16px; + font-weight: 700; + color: var(--km-g900); +} + +.km-actions { + display: flex; + gap: 8px; +} + +/* Cards */ +.km-card { + background: #fff; + border: 1px solid var(--km-g200); + border-radius: 12px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.km-ch { + padding: 13px 18px; + border-bottom: 1px solid var(--km-g100); + display: flex; + align-items: center; + gap: 10px; +} + +.km-ct { + font-size: 13px; + font-weight: 600; + color: var(--km-g800); +} + +.km-ch-hint { + margin-left: auto; + font-size: 12px; + color: var(--km-g500); +} + +.km-cb { + padding: 18px; +} + +.km-cb-flush { + padding: 0; +} + +.km-ci { + width: 28px; + height: 28px; + border-radius: 7px; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + flex-shrink: 0; +} + +.km-ci-t { background: var(--km-teal-l); color: var(--km-teal); } +.km-ci-a { background: var(--km-amber-l); color: var(--km-amber); } +.km-ci-r { background: var(--km-red-l); color: var(--km-red); } +.km-ci-b { background: var(--km-blue-l); color: var(--km-blue); } +.km-ci-g { background: var(--km-green-l); color: var(--km-green); } + +/* Badges */ +.km-b { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 9px; + border-radius: 20px; + font-size: 11px; + font-weight: 500; +} + +.km-b-gr { background: var(--km-green-l); color: var(--km-green); } +.km-b-am { background: var(--km-amber-l); color: var(--km-amber); } +.km-b-re { background: var(--km-red-l); color: var(--km-red); } +.km-b-or { background: var(--km-orange-l); color: var(--km-orange); } +.km-b-bl { background: var(--km-blue-l); color: var(--km-blue); } +.km-b-te { background: var(--km-teal-l); color: var(--km-teal); } +.km-b-gy { background: var(--km-g100); color: var(--km-g600); } +.km-b-nv { background: #e8edf5; color: var(--km-navy); } + +.km-ml-auto { margin-left: auto; } +.km-ml-12 { margin-left: 12px; } + +/* Buttons */ +.km-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border-radius: 8px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + border: 1px solid; + font-family: inherit; + transition: all 0.15s; +} + +.km-btn-t { background: var(--km-teal); border-color: var(--km-teal); color: #fff; &:hover { opacity: 0.9; } } +.km-btn-ot { background: #fff; border-color: var(--km-teal); color: var(--km-teal); &:hover { background: var(--km-teal-l); } } +.km-btn-n { background: var(--km-navy); border-color: var(--km-navy); color: #fff; } +.km-btn-a { background: var(--km-amber); border-color: var(--km-amber); color: #fff; } +.km-btn-r { background: var(--km-red); border-color: var(--km-red); color: #fff; } +.km-btn-g { background: #fff; border-color: var(--km-g300); color: var(--km-g600); &:hover { background: var(--km-g50); } } + +.km-w { width: 100%; } + +/* Stat row */ +.km-stat-row { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; + margin-bottom: 16px; +} + +.km-stat { + background: #fff; + border: 1px solid var(--km-g200); + border-radius: 12px; + padding: 14px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + cursor: pointer; + transition: all 0.15s; + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } +} + +.km-stat-n { + font-size: 26px; + font-weight: 700; + line-height: 1; + margin-bottom: 3px; +} + +.km-stat-l { + font-size: 12px; + color: var(--km-g500); +} + +.km-stat-t { + font-size: 11px; + margin-top: 5px; +} + +/* Filters */ +.km-filters { + display: flex; + gap: 10px; + margin-bottom: 14px; + flex-wrap: wrap; +} + +.km-fsel, +.km-finp { + padding: 7px 12px; + border: 1px solid var(--km-g200); + border-radius: 8px; + font-size: 12px; + color: var(--km-g700); + background: #fff; + font-family: inherit; + + &:focus { + outline: none; + border-color: var(--km-teal); + } +} + +.km-finp { + flex: 1; + min-width: 180px; +} + +.km-ta { + resize: none; +} + +/* Tables */ +.km-tbl { + width: 100%; + border-collapse: collapse; + + th { + text-align: left; + padding: 9px 12px; + font-size: 11px; + font-weight: 600; + color: var(--km-g500); + text-transform: uppercase; + letter-spacing: 0.05em; + background: var(--km-g50); + border-bottom: 1px solid var(--km-g200); + } + + td { + padding: 10px 12px; + border-bottom: 1px solid var(--km-g100); + font-size: 13px; + vertical-align: middle; + } + + tr:last-child td { + border-bottom: none; + } + + tbody tr:hover td { + background: var(--km-g50); + cursor: pointer; + } +} + +.km-an { + font-weight: 500; + color: var(--km-g900); +} + +.km-ai { + font-size: 11px; + color: var(--km-g400); + font-family: 'DM Mono', monospace; +} + +.km-num { + color: var(--km-g400); +} + +.km-small { + font-size: 11px; +} + +.km-mono { + font-family: 'DM Mono', monospace; + font-size: 12px; +} + +.km-red { + color: var(--km-red); +} + +/* State pills */ +.km-sp { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 9px; + border-radius: 20px; + font-size: 11px; + font-weight: 600; +} + +.km-sp-ac { background: var(--km-green-l); color: var(--km-green); } +.km-sp-fr { background: var(--km-amber-l); color: var(--km-amber); } +.km-sp-in { background: var(--km-orange-l); color: var(--km-orange); } +.km-sp-do { background: var(--km-red-l); color: var(--km-red); } +.km-sp-al { background: #fef9c3; color: #92400e; } + +/* Progress bar */ +.km-pbar { + display: flex; + align-items: center; + gap: 8px; +} + +.km-pbar-bg { + flex: 1; + height: 5px; + background: var(--km-g200); + border-radius: 3px; + overflow: hidden; +} + +.km-pbar-fill { + height: 100%; + border-radius: 3px; +} + +.km-pbar-n { + font-size: 12px; + font-weight: 600; + color: var(--km-amber); + min-width: 28px; +} + +/* Reference cell */ +.km-ref { + font-family: 'DM Mono', monospace; + font-size: 11px; + background: var(--km-g100); + padding: 2px 6px; + border-radius: 4px; + color: var(--km-g600); +} + +/* Delivery badges */ +.km-del { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 20px; + font-size: 10px; + font-weight: 600; +} + +.km-d-ok { background: var(--km-green-l); color: var(--km-green); } +.km-d-pe { background: var(--km-amber-l); color: var(--km-amber); } +.km-d-fa { background: var(--km-red-l); color: var(--km-red); } +.km-d-tr { background: var(--km-blue-l); color: var(--km-blue); } + +/* Pagination */ +.km-pagination { + padding: 11px 14px; + border-top: 1px solid var(--km-g100); + display: flex; + justify-content: space-between; + align-items: center; +} + +/* Notification scheduler grid */ +.km-notif-grid { + display: grid; + grid-template-columns: 1fr 310px; + gap: 14px; +} + +.km-side { + display: flex; + flex-direction: column; + gap: 12px; +} + +/* Notice timeline */ +.km-ntl { + display: flex; + flex-direction: column; +} + +.km-nt { + display: grid; + grid-template-columns: 36px 1fr; + gap: 12px; + padding: 13px 0; + border-bottom: 1px solid var(--km-g100); +} + +.km-nt-last { + border-bottom: none; + padding-bottom: 0; +} + +.km-nd-col { + display: flex; + flex-direction: column; + align-items: center; +} + +.km-nd { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + border: 2px solid; + flex-shrink: 0; +} + +.km-nd1 { background: var(--km-blue-l); border-color: #93c5fd; color: var(--km-blue); } +.km-nd2 { background: var(--km-amber-l); border-color: #fcd34d; color: var(--km-amber); } +.km-nd3 { background: var(--km-orange-l); border-color: #fdba74; color: var(--km-orange); } +.km-ndf { background: var(--km-red-l); border-color: #fca5a5; color: var(--km-red); } +.km-ndi { background: #fee2e2; border-color: #f87171; color: #991b1b; } +.km-ndd { background: var(--km-g100); border-color: var(--km-g300); color: var(--km-g500); } + +.km-nl { + width: 1px; + flex: 1; + background: var(--km-g200); + margin-top: 4px; +} + +.km-nh { + font-size: 13px; + font-weight: 600; + color: var(--km-g800); +} + +.km-warn { color: var(--km-amber); } +.km-warn-o { color: var(--km-orange); } +.km-warn-d { color: var(--km-g600); } + +.km-ns { + font-size: 11px; + color: var(--km-g500); + margin-top: 1px; + margin-bottom: 5px; +} + +.km-ok { color: var(--km-green); font-weight: 600; } +.km-pending { color: var(--km-amber); font-weight: 600; } +.km-fail { color: var(--km-red); font-weight: 600; } + +.km-ntags { + display: flex; + gap: 5px; + flex-wrap: wrap; +} + +.km-cp { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 20px; + font-size: 11px; + font-weight: 500; + border: 1px solid; +} + +.km-cp-sms { background: #eff6ff; color: #1d4ed8; border-color: #93c5fd; } +.km-cp-mail { background: #f0fdf4; color: #15803d; border-color: #86efac; } +.km-cp-ivr { background: #faf5ff; color: #7c3aed; border-color: #c4b5fd; } +.km-cp-post { background: #fff7ed; color: #c2410c; border-color: #fdba74; } +.km-cp-vis { background: var(--km-teal-l); color: var(--km-teal); border-color: #5eead4; } +.km-cp-vid { background: #ede9fe; color: #5b21b6; border-color: #a78bfa; } + +.km-row-actions { + margin-top: 8px; + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +/* Channels grid */ +.km-channels { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.km-channel { + border: 1px solid var(--km-g200); + border-radius: 8px; + padding: 10px; + cursor: pointer; +} + +.km-channel-on { + border-color: var(--km-teal); + background: var(--km-teal-l); +} + +.km-channel-i { font-size: 16px; margin-bottom: 3px; } +.km-channel-n { font-size: 11px; font-weight: 600; } +.km-channel-d { font-size: 10px; color: var(--km-g500); } + +/* Meta items */ +.km-mi { + background: var(--km-g50); + border: 1px solid var(--km-g100); + border-radius: 8px; + padding: 10px 12px; + margin-bottom: 8px; +} + +.km-ml { + font-size: 10px; + font-weight: 600; + color: var(--km-g400); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 3px; +} + +.km-mv { + font-size: 13px; + font-weight: 600; + color: var(--km-g800); +} + +/* Account status cards */ +.km-sc-row { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; + margin-bottom: 16px; +} + +.km-sc { + border: 1.5px solid; + border-radius: 12px; + padding: 14px; + cursor: pointer; + transition: all 0.15s; + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } +} + +.km-sc-ac { border-color: #86efac; background: var(--km-green-l); .km-sc-l, .km-sc-d { color: #166534; } .km-sc-n { color: var(--km-green); } } +.km-sc-fr { border-color: #fcd34d; background: var(--km-amber-l); .km-sc-l, .km-sc-d { color: #92400e; } .km-sc-n { color: var(--km-amber); } } +.km-sc-in { border-color: #fdba74; background: var(--km-orange-l); .km-sc-l, .km-sc-d { color: #7c2d12; } .km-sc-n { color: var(--km-orange); } } +.km-sc-do { border-color: #fca5a5; background: var(--km-red-l); .km-sc-l, .km-sc-d { color: #7f1d1d; } .km-sc-n { color: var(--km-red); } } +.km-sc-al { border-color: #fde047; background: #fef9c3; .km-sc-l, .km-sc-d { color: #713f12; } .km-sc-n { color: #92400e; } } + +.km-sc-l { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 3px; +} + +.km-sc-n { + font-size: 26px; + font-weight: 700; + margin-bottom: 2px; +} + +.km-sc-d { + font-size: 11px; +} + +/* 2-col layout */ +.km-r2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 14px; +} + +.km-mt { + margin-top: 14px; +} + +.km-mb { + margin-bottom: 14px; +} + +/* Rules */ +.km-rule { + padding: 11px 16px; + border-bottom: 1px solid var(--km-g100); +} + +.km-rule-last { + border-bottom: none; +} + +.km-rule-h { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4px; +} + +.km-rule-t { + font-weight: 600; + font-size: 13px; +} + +.km-rule-d { + font-size: 12px; + color: var(--km-g500); +} + +.km-rule-tags { + margin-top: 5px; + display: flex; + gap: 5px; + flex-wrap: wrap; +} + +/* Manual override warning */ +.km-warn-box { + margin-top: 10px; + padding: 10px; + background: var(--km-amber-l); + border-radius: 8px; + border: 1px solid #fcd34d; + font-size: 11px; +} + +.km-warn-t { + font-weight: 700; + color: var(--km-amber); +} + +.km-warn-d { + color: #92400e; + margin-top: 2px; +} + +/* MODAL */ +.km-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + z-index: 1200; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.km-modal { + background: #fff; + border-radius: 16px; + width: 900px; + max-width: 100%; + max-height: 90vh; + display: flex; + flex-direction: column; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); +} + +.km-mh { + padding: 18px 22px; + border-bottom: 1px solid var(--km-g200); + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +.km-mh-av { + width: 42px; + height: 42px; + border-radius: 50%; + background: linear-gradient(135deg, var(--km-teal), var(--km-navy-mid)); + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 700; + color: #fff; + flex-shrink: 0; +} + +.km-mh-name { + font-size: 16px; + font-weight: 700; + color: var(--km-g900); +} + +.km-mh-meta { + font-size: 12px; + color: var(--km-g500); + margin-top: 1px; +} + +.km-mclose { + width: 30px; + height: 30px; + border: 1px solid var(--km-g200); + border-radius: 7px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 15px; + color: var(--km-g500); + margin-left: auto; + flex-shrink: 0; + + &:hover { background: var(--km-g100); } +} + +.km-mbody { + overflow-y: auto; + flex: 1; + padding: 20px 22px; +} + +.km-mtab-bar { + display: flex; + gap: 2px; + background: var(--km-g100); + border-radius: 8px; + padding: 3px; + margin-bottom: 18px; +} + +.km-mtab { + flex: 1; + padding: 7px 8px; + font-size: 12px; + font-weight: 500; + text-align: center; + border-radius: 6px; + cursor: pointer; + color: var(--km-g500); + border: none; + background: transparent; + font-family: inherit; + + &.active { + background: #fff; + color: var(--km-g900); + border: 0.5px solid var(--km-g200); + font-weight: 600; + } +} + +.km-slbl { + font-size: 11px; + font-weight: 700; + color: var(--km-g400); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 10px; +} + +/* Stepper */ +.km-prog-steps { + display: flex; + align-items: flex-start; + gap: 0; + margin-bottom: 18px; +} + +.km-ps { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + position: relative; + + &:not(:last-child)::after { + content: ''; + position: absolute; + top: 14px; + left: 50%; + width: 100%; + height: 2px; + background: var(--km-g200); + z-index: 0; + } +} + +.km-ps-done::after { background: var(--km-teal); } + +.km-ps-dot { + width: 28px; + height: 28px; + border-radius: 50%; + background: #fff; + border: 2px solid var(--km-g300); + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + color: var(--km-g400); + z-index: 1; + position: relative; +} + +.km-ps-done .km-ps-dot { background: var(--km-teal); border-color: var(--km-teal); color: #fff; } +.km-ps-current .km-ps-dot { background: #fff; border-color: var(--km-amber); color: var(--km-amber); } +.km-ps-danger .km-ps-dot { background: #fff; border-color: var(--km-red); color: var(--km-red); } + +.km-ps-label { + font-size: 10px; + font-weight: 500; + color: var(--km-g500); + text-align: center; + max-width: 70px; +} + +.km-ps-done .km-ps-label { color: var(--km-teal); } +.km-ps-current .km-ps-label { color: var(--km-amber); font-weight: 600; } +.km-ps-danger .km-ps-label { color: var(--km-red); font-weight: 600; } + +/* Next action banner */ +.km-next-banner { + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 16px; + border: 1px solid; + display: flex; + align-items: center; + gap: 12px; +} + +.km-nb-alert { background: var(--km-amber-l); border-color: #fcd34d; } +.km-nb-urgent { background: var(--km-orange-l); border-color: #fdba74; } +.km-nb-freeze { background: var(--km-amber-l); border-color: #fcd34d; } +.km-nb-inop { background: var(--km-orange-l); border-color: #fdba74; } +.km-nb-dormant { background: var(--km-red-l); border-color: #fca5a5; } + +.km-nb-icon { font-size: 22px; flex-shrink: 0; } +.km-nb-title { font-size: 13px; font-weight: 700; margin-bottom: 2px; } +.km-nb-sub { font-size: 12px; } + +/* Modal stat strip */ +.km-mstat-row { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + margin-bottom: 16px; +} + +.km-mstat { + background: var(--km-g50); + border: 1px solid var(--km-g100); + border-radius: 8px; + padding: 12px; +} + +.km-mstat-n { + font-size: 20px; + font-weight: 700; + margin-bottom: 2px; + color: var(--km-teal); +} + +.km-mstat-l { + font-size: 11px; + color: var(--km-g500); +} + +/* Modal meta grid */ +.km-mg { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.km-mi-span { + grid-column: span 2; +} + +/* Modal timeline */ +.km-mtl { + display: flex; + flex-direction: column; +} + +.km-mti { + display: grid; + grid-template-columns: 28px 1fr; + gap: 10px; + padding: 12px 0; + border-bottom: 1px solid var(--km-g100); + + &:last-child { border-bottom: none; } +} + +.km-mti-dot { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + border: 2px solid; + flex-shrink: 0; +} + +.km-mti-done { background: var(--km-green-l); border-color: var(--km-green); color: var(--km-green); } +.km-mti-pend { background: var(--km-amber-l); border-color: var(--km-amber); color: var(--km-amber); } + +.km-mti-text { font-size: 13px; font-weight: 500; } +.km-done { color: var(--km-g700); } +.km-pend { color: var(--km-amber); } + +.km-mti-sub { font-size: 11px; color: var(--km-g400); margin-top: 2px; } + +.km-pending-row { + text-align: center; + color: var(--km-amber); + font-size: 12px; + padding: 12px; + background: #fffbeb; +} + +/* Empty state */ +.km-empty { + text-align: center; + padding: 40px; + color: var(--km-g400); +} + +.km-empty-i { font-size: 28px; margin-bottom: 8px; } +.km-empty-s { font-size: 12px; margin-top: 4px; } + +/* Evidence */ +.km-map { + background: var(--km-g100); + border: 1px solid var(--km-g200); + border-radius: 8px; + height: 110px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 6px; + font-size: 12px; + color: var(--km-g500); + margin-bottom: 14px; +} + +.km-photo { + background: var(--km-g100); + border: 1px solid var(--km-g200); + border-radius: 8px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + color: var(--km-g500); + margin-bottom: 14px; +} + +.km-notes { + background: var(--km-g50); + border: 1px solid var(--km-g200); + border-radius: 8px; + padding: 12px; + font-size: 12px; + color: var(--km-g700); + line-height: 1.6; +} + +/* Action panel */ +.km-action-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.km-action-row { + background: var(--km-g50); + border: 1px solid var(--km-g200); + border-radius: 8px; + padding: 12px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.km-action-t { font-size: 13px; font-weight: 600; } +.km-action-d { font-size: 12px; color: var(--km-g500); } + +/* Toast */ +.km-toast { + position: fixed; + bottom: 24px; + right: 24px; + background: var(--km-g900); + color: #fff; + padding: 10px 16px; + border-radius: 8px; + font-size: 13px; + z-index: 1300; + opacity: 0; + transform: translateY(8px); + transition: all 0.25s; + pointer-events: none; + + &.show { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 900px) { + .km-stat-row, + .km-sc-row { grid-template-columns: repeat(2, 1fr); } + .km-r2, + .km-notif-grid { grid-template-columns: 1fr; } + .km-mstat-row { grid-template-columns: repeat(2, 1fr); } + .km-prog-steps { flex-wrap: wrap; gap: 10px; } + .km-ps::after { display: none; } +} diff --git a/src/app/kyc-management/kyc-management.component.ts b/src/app/kyc-management/kyc-management.component.ts new file mode 100644 index 000000000..ab3b25b12 --- /dev/null +++ b/src/app/kyc-management/kyc-management.component.ts @@ -0,0 +1,485 @@ +/** + * 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 } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; + +interface KmNotice { + no: string; + channel: string; + date: string; + ref: string; + delivery: string; + response: string; +} + +interface KmEvidence { + type: 'visit' | 'vkyc'; + date: string; + officer: string; + gps?: string; + location?: string; + ref?: string; + outcome: string; + docket?: string; +} + +interface KmClient { + id: string; + av: string; + name: string; + meta: string; + state: 'alert' | 'freeze' | 'inop' | 'dormant' | 'urgent'; + risk: 'low' | 'mid' | 'high'; + riskLabel: string; + expiry: string; + daysLeft: number; + account: string; + kyc: { + type: string; + initiated: string; + lastVerified: string; + validity: string; + pan: string; + aadhaar: string; + panStatus: string; + aadhaarStatus: string; + panLink: string; + }; + contact: { + mobile: string; + email: string; + address: string; + branch: string; + }; + notices: KmNotice[]; + noticesSent: number; + noticePending: string; + nextStage: string; + pendingActions: string[]; + actionTaken: string[]; + evidence: KmEvidence | null; +} + +@Component({ + selector: 'mifosx-kyc-management', + templateUrl: './kyc-management.component.html', + styleUrls: ['./kyc-management.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule, + FaIconComponent + ] +}) +export class KycManagementComponent { + activeSection: 'alerts' | 'notif' | 'audit' | 'status' = 'alerts'; + + modalOpen = false; + modalTab: 'overview' | 'timeline' | 'notices' | 'evidence' | 'action' = 'overview'; + current: KmClient | null = null; + + toastMsg = ''; + toastVisible = false; + private toastTimer: any; + + clients: Record = { + rajesh: { + id: 'rajesh', + av: 'RS', + name: 'Rajesh Kumar Sharma', + meta: 'ACC-BPL-2024-00847 · Savings A/c · Bhopal Main', + account: 'ACC-BPL-2024-00847', + state: 'alert', + risk: 'low', + riskLabel: 'Low Risk', + expiry: '30 Apr 2027', + daysLeft: 15, + kyc: { + type: 'Individual', + initiated: '4 Apr 2026', + lastVerified: '4 Apr 2026', + validity: '10 Years', + pan: 'AQRPV8345E', + aadhaar: 'XXXXXXXX6039', + panStatus: 'Verified', + aadhaarStatus: 'Verified', + panLink: 'Linked' + }, + contact: { + mobile: '+91-XXXXX-99041', + email: 'r****@gmail.com', + address: 'House No. 42, Sector 5, Bhopal MP 462001', + branch: 'Bhopal Main' + }, + notices: [ + { no: 'N1', channel: 'SMS + Email', date: '31 Mar 2027', ref: 'MSG-2027-0331-001', delivery: 'Delivered', response: 'No Action' }, + { no: 'N2', channel: 'Reg. Post + IVR', date: '10 Apr 2027', ref: 'RP-BPL-20270410-443', delivery: 'In Transit', response: 'Pending' } + ], + noticesSent: 2, + noticePending: 'Notice 3', + nextStage: 'Debit Freeze', + pendingActions: ['Send Notice 3 (due 20 Apr 2027)', 'Schedule Branch Visit', 'Send Video KYC link'], + actionTaken: ['Notice 1 delivered', 'Notice 2 dispatched via Reg. Post'], + evidence: null + }, + priya: { + id: 'priya', + av: 'PM', + name: 'Priya Malhotra', + meta: 'ACC-BPL-2021-00231 · Savings A/c · Bhopal Main', + account: 'ACC-BPL-2021-00231', + state: 'freeze', + risk: 'mid', + riskLabel: 'Mid Risk', + expiry: '10 Apr 2027', + daysLeft: 0, + kyc: { + type: 'Individual', + initiated: '10 Apr 2019', + lastVerified: '10 Apr 2019', + validity: '8 Years', + pan: 'BKJPV1234A', + aadhaar: 'XXXXXXXX7721', + panStatus: 'Verified', + aadhaarStatus: 'Verified', + panLink: 'Linked' + }, + contact: { + mobile: '+91-XXXXX-55321', + email: 'p****@yahoo.com', + address: 'Flat 12B, Arera Colony, Bhopal MP 462016', + branch: 'Bhopal Main' + }, + notices: [ + { no: 'N1', channel: 'SMS + Email', date: '10 Mar 2027', ref: 'MSG-2027-0310-003', delivery: 'Delivered', response: 'No Response' }, + { no: 'N2', channel: 'Reg. Post', date: '20 Mar 2027', ref: 'RP-BPL-20270320-091', delivery: 'Delivered', response: 'No Response' }, + { no: 'N3', channel: 'Branch Visit', date: '30 Mar 2027', ref: 'VIS-BPL-20270330-012', delivery: 'Visit Done', response: 'Refused' } + ], + noticesSent: 3, + noticePending: 'None — all 3 sent', + nextStage: 'Inoperative by 30 Jun 2027', + pendingActions: ['Account in Debit Freeze', 'Inoperative transition due 30 Jun 2027', 'Maker-checker unfreeze pending'], + actionTaken: ['Notice 1 delivered', 'Notice 2 delivered via Reg. Post', 'Notice 3 — Branch visit done, client refused', 'Debit Freeze applied 10 Apr 2027'], + evidence: { + type: 'visit', + date: '30 Mar 2027', + officer: 'Anupam Mishra', + gps: '23.2599° N, 77.4126° E', + location: 'Arera Colony, Bhopal', + outcome: 'Client refused to sign self-declaration form. Informed about debit freeze implications.', + docket: 'VIS-BPL-20270330-012' + } + }, + suresh: { + id: 'suresh', + av: 'SP', + name: 'Suresh Patel', + meta: 'ACC-BPL-2019-00112 · Savings A/c · Bhopal South', + account: 'ACC-BPL-2019-00112', + state: 'inop', + risk: 'high', + riskLabel: 'High Risk', + expiry: '15 Mar 2027', + daysLeft: 0, + kyc: { + type: 'Individual', + initiated: '15 Mar 2025', + lastVerified: '15 Mar 2025', + validity: '2 Years', + pan: 'CXRTS5678B', + aadhaar: 'XXXXXXXX2211', + panStatus: 'Verified', + aadhaarStatus: 'Verified', + panLink: 'Linked' + }, + contact: { + mobile: '+91-XXXXX-88765', + email: 's****@gmail.com', + address: 'Village Ratibad, Bhopal MP 462044', + branch: 'Bhopal South' + }, + notices: [ + { no: 'N1', channel: 'SMS + Email', date: '13 Feb 2027', ref: 'MSG-2027-0213-006', delivery: 'Delivered', response: 'No Response' }, + { no: 'N2', channel: 'Reg. Post + IVR', date: '23 Feb 2027', ref: 'RP-BPL-20270223-044', delivery: 'Delivered', response: 'No Response' }, + { no: 'N3', channel: 'Video KYC', date: '5 Mar 2027', ref: 'VKYC-2027-0305-007', delivery: 'No Response', response: 'No Action' } + ], + noticesSent: 3, + noticePending: 'None', + nextStage: 'Dormant by 30 Sep 2027', + pendingActions: ['Account Inoperative — all txns blocked', 'Dormant transition due 30 Sep 2027', 'RBI Dormant Account reporting'], + actionTaken: ['Notice 1 delivered', 'Notice 2 delivered via Reg. Post', 'Notice 3 — Video KYC invite sent (no response)', 'Debit Freeze 15 Mar 2027', 'Inoperative 15 May 2027'], + evidence: { + type: 'vkyc', + date: '5 Mar 2027', + officer: 'System', + ref: 'VKYC-2027-0305-007', + outcome: 'Video KYC session link sent. Client did not join within 48 hours. Session auto-expired.' + } + }, + kavitha: { + id: 'kavitha', + av: 'KN', + name: 'Kavitha Nair', + meta: 'ACC-BPL-2018-00088 · Savings A/c · Bhopal East', + account: 'ACC-BPL-2018-00088', + state: 'dormant', + risk: 'mid', + riskLabel: 'Mid Risk', + expiry: '1 Jan 2027', + daysLeft: 0, + kyc: { + type: 'Individual', + initiated: '1 Jan 2019', + lastVerified: '1 Jan 2019', + validity: '8 Years', + pan: 'DVJNK9012C', + aadhaar: 'XXXXXXXX4492', + panStatus: 'Verified', + aadhaarStatus: 'Verified', + panLink: 'Linked' + }, + contact: { + mobile: '+91-XXXXX-11092', + email: 'k****@rediff.com', + address: 'Plot 7, Awadhpuri, Bhopal MP 462010', + branch: 'Bhopal East' + }, + notices: [ + { no: 'N1', channel: 'SMS + Email', date: '1 Dec 2026', ref: 'MSG-2026-1201-009', delivery: 'Delivered', response: 'No Response' }, + { no: 'N2', channel: 'Reg. Post', date: '11 Dec 2026', ref: 'RP-BPL-20261211-077', delivery: 'Delivered', response: 'No Response' }, + { no: 'N3', channel: 'Branch Visit', date: '22 Dec 2026', ref: 'VIS-BPL-20261222-019', delivery: 'Not Found', response: 'Not Found at Address' } + ], + noticesSent: 3, + noticePending: 'None', + nextStage: 'Dormant (Final) — RBI Reporting', + pendingActions: ['RBI Dormant Account Report submission', 'Branch Manager approval for any reversal', 'Regional Manager sign-off required'], + actionTaken: ['Notice 1–3 sent, all no response', 'Debit Freeze 1 Jan 2027', 'Inoperative 2 Mar 2027', 'Dormant 1 Jun 2027', 'RBI report auto-generated'], + evidence: { + type: 'visit', + date: '22 Dec 2026', + officer: 'Rekha Sharma', + gps: '23.2491° N, 77.4384° E', + location: 'Awadhpuri, Bhopal', + outcome: 'Client not found at registered address. Neighbours confirmed relocation. Address verification required.', + docket: 'VIS-BPL-20261222-019' + } + }, + amit: { + id: 'amit', + av: 'AS', + name: 'Amit Singh', + meta: 'ACC-BPL-2024-01103 · Savings A/c · Bhopal Main', + account: 'ACC-BPL-2024-01103', + state: 'alert', + risk: 'low', + riskLabel: 'Low Risk', + expiry: '5 May 2027', + daysLeft: 20, + kyc: { + type: 'Individual', + initiated: '5 May 2016', + lastVerified: '5 May 2016', + validity: '10 Years', + pan: 'EKJPS4321D', + aadhaar: 'XXXXXXXX8834', + panStatus: 'Verified', + aadhaarStatus: 'Verified', + panLink: 'Linked' + }, + contact: { + mobile: '+91-XXXXX-22334', + email: 'a****@gmail.com', + address: 'C-22, Shivaji Nagar, Bhopal MP 462016', + branch: 'Bhopal Main' + }, + notices: [ + { no: 'N1', channel: 'SMS + Email', date: '5 Apr 2027', ref: 'MSG-2027-0405-011', delivery: 'Delivered', response: 'No Action' } + ], + noticesSent: 1, + noticePending: 'Notice 2 & 3', + nextStage: 'Debit Freeze if no action', + pendingActions: ['Send Notice 2 (due 15 Apr 2027)', 'Send Notice 3 (due 25 Apr 2027)', 'Branch visit if needed'], + actionTaken: ['Notice 1 delivered 5 Apr 2027'], + evidence: null + }, + meena: { + id: 'meena', + av: 'MJ', + name: 'Meena Joshi', + meta: 'ACC-BPL-2022-00567 · Savings A/c · Bhopal North', + account: 'ACC-BPL-2022-00567', + state: 'urgent', + risk: 'high', + riskLabel: 'High Risk', + expiry: '28 Apr 2027', + daysLeft: 3, + kyc: { + type: 'Individual', + initiated: '28 Apr 2025', + lastVerified: '28 Apr 2025', + validity: '2 Years', + pan: 'FJLMN6789E', + aadhaar: 'XXXXXXXX3309', + panStatus: 'Verified', + aadhaarStatus: 'Verified', + panLink: 'Linked' + }, + contact: { + mobile: '+91-XXXXX-77654', + email: 'm****@gmail.com', + address: 'Lane 4, Shyamla Hills, Bhopal MP 462002', + branch: 'Bhopal North' + }, + notices: [ + { no: 'N1', channel: 'SMS + Email', date: '29 Mar 2027', ref: 'MSG-2027-0329-014', delivery: 'Delivered', response: 'No Action' }, + { no: 'N2', channel: 'Reg. Post + IVR', date: '8 Apr 2027', ref: 'RP-BPL-20270408-088', delivery: 'Delivered', response: 'No Response' } + ], + noticesSent: 2, + noticePending: 'URGENT: Notice 3', + nextStage: 'Debit Freeze in 3 days', + pendingActions: ['URGENT: Send Notice 3 NOW', 'Schedule Branch Visit immediately', 'Consider Video KYC as last resort'], + actionTaken: ['Notice 1 delivered', 'Notice 2 delivered via Reg. Post + IVR'], + evidence: null + } + }; + + alertRows: KmClient[] = []; + + ngOnInit(): void { + this.alertRows = Object.values(this.clients); + } + + showSection(section: 'alerts' | 'notif' | 'audit' | 'status'): void { + this.activeSection = section; + } + + openModal(id: string): void { + const c = this.clients[id]; + if (!c) return; + this.current = c; + this.modalTab = 'overview'; + this.modalOpen = true; + } + + closeModal(): void { + this.modalOpen = false; + this.current = null; + } + + showMTab(tab: 'overview' | 'timeline' | 'notices' | 'evidence' | 'action'): void { + this.modalTab = tab; + } + + toast(msg: string): void { + this.toastMsg = msg; + this.toastVisible = true; + if (this.toastTimer) clearTimeout(this.toastTimer); + this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800); + } + + /** Compliance journey stepper state for a given client */ + getStepperState(c: KmClient): { label: string; state: 'done' | 'pending' | 'current' | 'danger' }[] { + return [ + { label: 'KYC Active', state: 'done' }, + { label: 'Alert Sent', state: c.noticesSent >= 1 ? 'done' : 'pending' }, + { label: 'Notice 2', state: c.noticesSent >= 2 ? 'done' : c.noticesSent >= 1 ? 'current' : 'pending' }, + { + label: 'Notice 3', + state: + c.noticesSent >= 3 + ? 'done' + : c.noticesSent >= 2 + ? c.state === 'urgent' + ? 'danger' + : 'current' + : 'pending' + }, + { + label: 'Debit Freeze', + state: ['freeze', 'inop', 'dormant'].includes(c.state) ? 'done' : c.noticesSent >= 3 ? 'danger' : 'pending' + }, + { label: 'Inoperative', state: ['inop', 'dormant'].includes(c.state) ? 'done' : 'pending' }, + { label: 'Dormant', state: c.state === 'dormant' ? 'danger' : 'pending' } + ]; + } + + stepIcon(state: string): string { + if (state === 'done') return '✓'; + if (state === 'danger') return '!'; + return '·'; + } + + stateLabel(state: string): string { + return ( + { + alert: 'Alert Window', + freeze: 'Debit Freeze', + inop: 'Inoperative', + dormant: 'Dormant', + urgent: 'Urgent Alert' + } as Record + )[state] || state; + } + + stateClass(state: string): string { + return ( + { + alert: 'sp-al', + freeze: 'sp-fr', + inop: 'sp-in', + dormant: 'sp-do', + urgent: 'sp-fr' + } as Record + )[state] || 'sp-al'; + } + + riskClass(risk: string): string { + return ({ low: 'b-gr', mid: 'b-am', high: 'b-re' } as Record)[risk] || 'b-gy'; + } + + /** Notices pending count (cap at 0) */ + noticesPending(c: KmClient): number { + return Math.max(0, 3 - c.noticesSent); + } + + deliveryClass(d: string): string { + const m: Record = { + Delivered: 'd-ok', + 'In Transit': 'd-tr', + 'No Response': 'd-fa', + Pending: 'd-pe', + 'Visit Done': 'd-ok', + 'Not Found': 'd-fa' + }; + return m[d] || 'd-pe'; + } + + barWidth(c: KmClient): number { + if (c.daysLeft <= 0) return 100; + return Math.max(2, Math.min(100, c.daysLeft * 3)); + } + + barColor(c: KmClient): string { + if (c.daysLeft <= 0) return 'var(--km-g400)'; + return c.daysLeft > 10 ? 'var(--km-amber)' : 'var(--km-red)'; + } + + responseClass(r: string): string { + const m: Record = { + 'No Action': 'd-pe', + 'No Response': 'd-fa', + Pending: 'd-pe', + Refused: 'd-fa', + 'Not Found at Address': 'd-fa', + Awaited: 'd-pe' + }; + return m[r] || 'd-pe'; + } +} diff --git a/src/app/kyc-management/kyc-management.module.ts b/src/app/kyc-management/kyc-management.module.ts new file mode 100644 index 000000000..77ced18c1 --- /dev/null +++ b/src/app/kyc-management/kyc-management.module.ts @@ -0,0 +1,22 @@ +/** + * 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 { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared/shared.module'; +import { KycManagementRoutingModule } from './kyc-management-routing.module'; +import { KycManagementComponent } from './kyc-management.component'; + +@NgModule({ + imports: [ + SharedModule, + KycManagementRoutingModule, + KycManagementComponent + ] +}) +export class KycManagementModule {} diff --git a/src/app/login/login-form/login-form.component.scss b/src/app/login/login-form/login-form.component.scss index c5c70b4db..7d94da5f7 100644 --- a/src/app/login/login-form/login-form.component.scss +++ b/src/app/login/login-form/login-form.component.scss @@ -43,23 +43,23 @@ $text-muted: #7a96bc; box-sizing: border-box; width: 100%; height: 42px; - background: rgba(255, 255, 255, 0.045); - border: 1px solid rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 4.5%); + border: 1px solid rgb(255 255 255 / 10%); border-radius: 8px; padding: 0 44px 0 38px; color: #fff; font-size: 0.82rem; - font-family: 'Inter', sans-serif; + font-family: Inter, sans-serif; outline: none; transition: border-color 0.2s, background 0.2s; &:focus { border-color: $accent; - background: rgba(0, 180, 216, 0.07); + background: rgb(0 180 216 / 7%); } &::placeholder { - color: rgba(255, 255, 255, 0.22); + color: rgb(255 255 255 / 22%); } } } @@ -106,9 +106,9 @@ $text-muted: #7a96bc; .remember-checkbox { ::ng-deep { - .mdc-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate='true']) + .mdc-checkbox .mdc-checkbox__native-control:enabled:not(:checked, :indeterminate, [data-indeterminate='true']) ~ .mdc-checkbox__background { - border-color: rgba(255, 255, 255, 0.4); + border-color: rgb(255 255 255 / 40%); } .mdc-checkbox .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background { @@ -117,7 +117,7 @@ $text-muted: #7a96bc; } .mdc-label { - color: rgba(255, 255, 255, 0.7); + color: rgb(255 255 255 / 70%); font-size: 0.82rem; } } @@ -130,7 +130,7 @@ $text-muted: #7a96bc; ::ng-deep { .mdc-linear-progress__bar-inner { border-color: $accent; } - .mdc-linear-progress__buffer-bar { background-color: rgba(255, 255, 255, 0.1); } + .mdc-linear-progress__buffer-bar { background-color: rgb(255 255 255 / 10%); } } } @@ -140,7 +140,7 @@ $text-muted: #7a96bc; } .btn-login { - -webkit-appearance: none; + appearance: none; appearance: none; display: block; box-sizing: border-box; @@ -153,7 +153,7 @@ $text-muted: #7a96bc; border: none; border-radius: 8px; color: #fff; - font-family: 'Inter', sans-serif; + font-family: Inter, sans-serif; font-size: 1rem; font-weight: 700; letter-spacing: 0.12em; @@ -162,7 +162,7 @@ $text-muted: #7a96bc; text-transform: uppercase; cursor: pointer; transition: opacity 0.2s, transform 0.1s; - box-shadow: 0 4px 20px rgba(0, 180, 216, 0.25); + box-shadow: 0 4px 20px rgb(0 180 216 / 25%); &:hover { opacity: 0.88; transform: translateY(-1px); } &:active { transform: translateY(0); } @@ -176,7 +176,7 @@ $text-muted: #7a96bc; } // ═══ Responsive ═══ -@media (max-width: 480px) { +@media (width <= 480px) { .field-wrap input { font-size: 0.9rem; padding: 9px 12px 9px 36px; } .btn-login { padding: 10px; font-size: 0.9rem; } } diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss index bed935528..38559830d 100644 --- a/src/app/login/login.component.scss +++ b/src/app/login/login.component.scss @@ -22,7 +22,7 @@ $text-muted: #7a96bc; &__dots-bg { position: absolute; inset: 0; - background-image: radial-gradient(circle, rgba(0, 180, 216, 0.18) 1px, transparent 1px); + background-image: radial-gradient(circle, rgb(0 180 216 / 18%) 1px, transparent 1px); background-size: 34px 34px; opacity: 0.35; pointer-events: none; } } @@ -57,13 +57,13 @@ $text-muted: #7a96bc; &__icon { font-size: 3.4rem; margin-bottom: 14px; display: block; - filter: drop-shadow(0 0 14px rgba(0, 180, 216, 0.4)); line-height: 1; + filter: drop-shadow(0 0 14px rgb(0 180 216 / 40%)); line-height: 1; } &__title { font-size: 2.55rem; font-weight: 700; line-height: 1.1; margin-bottom: 14px; max-width: 500px; - text-shadow: 0 2px 24px rgba(0, 0, 0, 0.5); + text-shadow: 0 2px 24px rgb(0 0 0 / 50%); } &__desc { @@ -96,14 +96,15 @@ $text-muted: #7a96bc; @include slide-theme('s1', #00b4d8); @include slide-theme('s2', #34d05c); .s2 .slide__title { font-size: 2.1rem; } + @include slide-theme('s3', #ffcc55); @include slide-theme('s4', #ff7070); @include slide-theme('s5', #b49eff); @include slide-theme('s6', #00e5b0); .stat-chip { - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 4%); + border: 1px solid rgb(255 255 255 / 10%); border-radius: 10px; padding: 10px 18px; backdrop-filter: blur(6px); @@ -112,8 +113,8 @@ $text-muted: #7a96bc; } .flow-node { - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(0, 180, 216, 0.25); + background: rgb(255 255 255 / 4%); + border: 1px solid rgb(0 180 216 / 25%); border-radius: 10px; padding: 9px 18px; font-size: 0.71rem; font-weight: 600; letter-spacing: 0.06em; white-space: nowrap; text-align: center; @@ -122,12 +123,12 @@ $text-muted: #7a96bc; .flow-arrow { width: 1px; height: 20px; position: relative; - background: linear-gradient(to bottom, rgba(0, 180, 216, 0.45), rgba(0, 180, 216, 0.05)); + background: linear-gradient(to bottom, rgb(0 180 216 / 45%), rgb(0 180 216 / 5%)); &::after { content: '▼'; position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%); font-size: 0.38rem; - color: rgba(0, 180, 216, 0.45); + color: rgb(0 180 216 / 45%); } } @@ -139,7 +140,7 @@ $text-muted: #7a96bc; .slide-dot { width: 7px; height: 7px; border-radius: 50%; - background: rgba(255, 255, 255, 0.2); + background: rgb(255 255 255 / 20%); cursor: pointer; border: none; transition: all 0.3s; &.active { background: $accent; width: 22px; border-radius: 4px; } @@ -159,6 +160,7 @@ $text-muted: #7a96bc; } @keyframes pbfill { from { width: 0%; } to { width: 100%; } } + @keyframes dpulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(0.6); } } // ═══ RIGHT PANEL — LOGIN ═══ @@ -168,12 +170,12 @@ $text-muted: #7a96bc; justify-content: center; align-items: center; padding: 40px 48px; position: relative; overflow: hidden; background: linear-gradient(160deg, #07101f 0%, #0b1d35 65%, #081528 100%); - border-left: 1px solid rgba(0, 180, 216, 0.14); + border-left: 1px solid rgb(0 180 216 / 14%); &::before { content: ''; position: absolute; top: -130px; right: -130px; width: 500px; height: 500px; border-radius: 50%; - background: radial-gradient(circle, rgba(0, 180, 216, 0.07) 0%, transparent 65%); + background: radial-gradient(circle, rgb(0 180 216 / 7%) 0%, transparent 65%); pointer-events: none; } } @@ -203,44 +205,44 @@ $text-muted: #7a96bc; .right-footer { position: absolute; bottom: 18px; left: 0; right: 0; text-align: center; font-size: 0.61rem; - color: rgba(255, 255, 255, 0.18); letter-spacing: 0.05em; + color: rgb(255 255 255 / 18%); letter-spacing: 0.05em; } // ═══ SETTINGS ═══ .settings-btn { position: absolute; top: 16px; right: 16px; z-index: 100; width: 42px; height: 42px; border-radius: 50%; - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.08); + border: 1px solid rgb(255 255 255 / 20%); + background: rgb(255 255 255 / 8%); backdrop-filter: blur(12px); color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; &:hover { - background: rgba(255, 255, 255, 0.15); + background: rgb(255 255 255 / 15%); transform: rotate(45deg); } } .settings-overlay { position: fixed; inset: 0; z-index: 99; - background: rgba(0, 0, 0, 0.3); backdrop-filter: blur(2px); + background: rgb(0 0 0 / 30%); backdrop-filter: blur(2px); } .settings-panel { position: absolute; top: 64px; right: 16px; z-index: 101; width: 320px; max-width: calc(100vw - 2rem); - background: rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 10%); backdrop-filter: blur(24px) saturate(1.5); border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.15); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + border: 1px solid rgb(255 255 255 / 15%); + box-shadow: 0 8px 32px rgb(0 0 0 / 30%); overflow: hidden; &__header { padding: 16px 20px 10px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); + border-bottom: 1px solid rgb(255 255 255 / 10%); display: flex; align-items: center; gap: 8px; h3 { margin: 0; font-size: 0.95rem; font-weight: 600; color: #fff; } @@ -255,12 +257,12 @@ $text-muted: #7a96bc; .settings-item { display: flex; align-items: center; justify-content: space-between; - padding: 6px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.06); + padding: 6px 0; border-bottom: 1px solid rgb(255 255 255 / 6%); &:last-child { border-bottom: none; } &__label { - font-size: 0.82rem; font-weight: 500; color: rgba(255, 255, 255, 0.85); + font-size: 0.82rem; font-weight: 500; color: rgb(255 255 255 / 85%); display: flex; align-items: center; gap: 8px; mifosx-m3-icon { font-size: 18px; opacity: 0.7; } @@ -273,13 +275,13 @@ $text-muted: #7a96bc; #tenant-selector { width: 100%; margin: 0; - .mat-mdc-text-field-wrapper { border-radius: 8px; background: rgba(255, 255, 255, 0.06); } + .mat-mdc-text-field-wrapper { border-radius: 8px; background: rgb(255 255 255 / 6%); } .mat-mdc-form-field-flex { height: 36px; } .mat-mdc-form-field-infix { padding: 6px 0; min-height: 36px; } - .mat-mdc-floating-label { color: rgba(255, 255, 255, 0.5); font-size: 0.8rem; } + .mat-mdc-floating-label { color: rgb(255 255 255 / 50%); font-size: 0.8rem; } .mat-mdc-select-value { color: #fff; font-size: 0.82rem; } - .mat-mdc-select-arrow { color: rgba(255, 255, 255, 0.6); } - .mat-mdc-form-field-icon-prefix { color: rgba(255, 255, 255, 0.4); padding: 0 6px 0 4px; } + .mat-mdc-select-arrow { color: rgb(255 255 255 / 60%); } + .mat-mdc-form-field-icon-prefix { color: rgb(255 255 255 / 40%); padding: 0 6px 0 4px; } .mdc-line-ripple::after { border-bottom-color: $accent; } } } @@ -292,13 +294,14 @@ $text-muted: #7a96bc; width: 100%; margin: 0; .mat-mdc-form-field-flex { height: 36px; } .mat-mdc-form-field-infix { padding: 6px 0; min-height: 36px; } + .mdc-notched-outline { - .mdc-notched-outline__leading { border-radius: 8px 0 0 8px; border-color: rgba(255, 255, 255, 0.2); } - .mdc-notched-outline__notch { border-color: rgba(255, 255, 255, 0.2); } - .mdc-notched-outline__trailing { border-radius: 0 8px 8px 0; border-color: rgba(255, 255, 255, 0.2); } + .mdc-notched-outline__leading { border-radius: 8px 0 0 8px; border-color: rgb(255 255 255 / 20%); } + .mdc-notched-outline__notch { border-color: rgb(255 255 255 / 20%); } + .mdc-notched-outline__trailing { border-radius: 0 8px 8px 0; border-color: rgb(255 255 255 / 20%); } } .mat-mdc-select-value { color: #fff; } - .mat-mdc-select-arrow { color: rgba(255, 255, 255, 0.6); } + .mat-mdc-select-arrow { color: rgb(255 255 255 / 60%); } } } @@ -310,47 +313,49 @@ $text-muted: #7a96bc; width: 100%; margin: 0; .mat-mdc-form-field-flex { height: 36px; } .mat-mdc-form-field-infix { padding: 6px 0; min-height: 36px; } + .mdc-notched-outline { - .mdc-notched-outline__leading { border-radius: 8px 0 0 8px; border-color: rgba(255, 255, 255, 0.2); } - .mdc-notched-outline__notch { border-color: rgba(255, 255, 255, 0.2); } - .mdc-notched-outline__trailing { border-radius: 0 8px 8px 0; border-color: rgba(255, 255, 255, 0.2); } + .mdc-notched-outline__leading { border-radius: 8px 0 0 8px; border-color: rgb(255 255 255 / 20%); } + .mdc-notched-outline__notch { border-color: rgb(255 255 255 / 20%); } + .mdc-notched-outline__trailing { border-radius: 0 8px 8px 0; border-color: rgb(255 255 255 / 20%); } } .mat-mdc-select-value { color: #fff; } - .mat-mdc-select-arrow { color: rgba(255, 255, 255, 0.6); } + .mat-mdc-select-arrow { color: rgb(255 255 255 / 60%); } } } // Theme toggle ::ng-deep mifosx-theme-toggle { - button { color: #fff; &:hover { background: rgba(255, 255, 255, 0.1); } } + button { color: #fff; &:hover { background: rgb(255 255 255 / 10%); } } } } // ═══ FOOTER ═══ .login-footer { - flex-shrink: 0; background: rgba(3, 8, 18, 0.97); - border-top: 1px solid rgba(0, 180, 216, 0.17); + flex-shrink: 0; background: rgb(3 8 18 / 97%); + border-top: 1px solid rgb(0 180 216 / 17%); padding: 7px 32px; display: flex; align-items: center; justify-content: center; gap: 10px; &__text { font-size: 0.61rem; color: $text-muted; letter-spacing: 0.03em; text-align: center; line-height: 1.5; - strong { color: rgba(0, 180, 216, 0.9); font-weight: 600; } + strong { color: rgb(0 180 216 / 90%); font-weight: 600; } } - &__sep { color: rgba(255, 255, 255, 0.12); font-size: 0.75rem; } + &__sep { color: rgb(255 255 255 / 12%); font-size: 0.75rem; } } // ═══ RESPONSIVE ═══ -@media (max-width: 1024px) { +@media (width <= 1024px) { .left-panel { flex: 0 0 55%; } .right-panel { flex: 0 0 45%; padding: 32px; } .slide__title { font-size: 2rem; } .slide__flow { display: none; } } -@media (max-width: 768px) { +@media (width <= 768px) { .login-split { flex-direction: column; } .left-panel { display: none; } + .right-panel { flex: 1; padding: 24px; background: linear-gradient(160deg, #060f1e 0%, #0b1d35 100%); diff --git a/src/app/organization/adhoc-query/create-adhoc-query/create-adhoc-query.component.scss b/src/app/organization/adhoc-query/create-adhoc-query/create-adhoc-query.component.scss index 25c688b6d..bf9767eaf 100644 --- a/src/app/organization/adhoc-query/create-adhoc-query/create-adhoc-query.component.scss +++ b/src/app/organization/adhoc-query/create-adhoc-query/create-adhoc-query.component.scss @@ -43,7 +43,8 @@ $gray-900: #202124; .caq-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .caq-field { diff --git a/src/app/organization/bulk-import/bulk-import.component.scss b/src/app/organization/bulk-import/bulk-import.component.scss index 11b969490..c6c3aeec8 100644 --- a/src/app/organization/bulk-import/bulk-import.component.scss +++ b/src/app/organization/bulk-import/bulk-import.component.scss @@ -62,7 +62,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -83,7 +83,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; background: $gray-50; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + box-shadow: 0 2px 8px rgb(0 0 0 / 4%); .org-card__arrow { color: $gray-700; @@ -102,62 +102,62 @@ $gray-900: #0f172a; flex-shrink: 0; &--blue { - background: rgba(59, 130, 246, 0.1); + background: rgb(59 130 246 / 10%); color: #2563eb; } &--amber { - background: rgba(245, 158, 11, 0.1); + background: rgb(245 158 11 / 10%); color: #d97706; } &--green { - background: rgba(34, 197, 94, 0.1); + background: rgb(34 197 94 / 10%); color: #16a34a; } &--emerald { - background: rgba(16, 185, 129, 0.1); + background: rgb(16 185 129 / 10%); color: #059669; } &--purple { - background: rgba(139, 92, 246, 0.1); + background: rgb(139 92 246 / 10%); color: #7c3aed; } &--indigo { - background: rgba(99, 102, 241, 0.1); + background: rgb(99 102 241 / 10%); color: #4f46e5; } &--slate { - background: rgba(100, 116, 139, 0.1); + background: rgb(100 116 139 / 10%); color: #475569; } &--orange { - background: rgba(249, 115, 22, 0.1); + background: rgb(249 115 22 / 10%); color: #ea580c; } &--teal { - background: rgba(20, 184, 166, 0.1); + background: rgb(20 184 166 / 10%); color: #0d9488; } &--cyan { - background: rgba(6, 182, 212, 0.1); + background: rgb(6 182 212 / 10%); color: #0891b2; } &--rose { - background: rgba(244, 63, 94, 0.1); + background: rgb(244 63 94 / 10%); color: #e11d48; } &--red { - background: rgba(239, 68, 68, 0.1); + background: rgb(239 68 68 / 10%); color: #dc2626; } } diff --git a/src/app/organization/bulk-loan-reassignmnet/bulk-loan-reassignmnet.component.scss b/src/app/organization/bulk-loan-reassignmnet/bulk-loan-reassignmnet.component.scss index 54c344613..c306fd8e0 100644 --- a/src/app/organization/bulk-loan-reassignmnet/bulk-loan-reassignmnet.component.scss +++ b/src/app/organization/bulk-loan-reassignmnet/bulk-loan-reassignmnet.component.scss @@ -57,7 +57,7 @@ $gray-900: #202124; border: 1px solid $border; border-radius: 12px; padding: 0; - box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04); + box-shadow: 0 1px 3px rgb(16 24 40 / 6%), 0 1px 2px rgb(16 24 40 / 4%); } .bl-section { @@ -89,7 +89,7 @@ $gray-900: #202124; grid-template-columns: 1fr 1fr; gap: 4px 20px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -104,7 +104,7 @@ $gray-900: #202124; grid-template-columns: 1fr 1fr; gap: 20px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -184,7 +184,7 @@ $gray-900: #202124; font-weight: 500; font-size: 13px; padding: 4px 20px !important; - box-shadow: 0 1px 2px rgba(19, 83, 160, 0.15); + box-shadow: 0 1px 2px rgb(19 83 160 / 15%); transition: all 0.2s ease; i { @@ -194,7 +194,7 @@ $gray-900: #202124; &:hover { background: #0f4688 !important; - box-shadow: 0 2px 6px rgba(19, 83, 160, 0.25); + box-shadow: 0 2px 6px rgb(19 83 160 / 25%); } &:disabled { diff --git a/src/app/organization/currencies/manage-currencies/manage-currencies.component.scss b/src/app/organization/currencies/manage-currencies/manage-currencies.component.scss index 8f6ad4669..726dcca37 100644 --- a/src/app/organization/currencies/manage-currencies/manage-currencies.component.scss +++ b/src/app/organization/currencies/manage-currencies/manage-currencies.component.scss @@ -73,7 +73,7 @@ $gray-900: #202124; justify-content: space-between; padding: 10px 14px; background: $gray-50; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); border-radius: 8px; margin-bottom: 6px; transition: background 0.12s; @@ -110,7 +110,7 @@ $gray-900: #202124; line-height: 32px !important; font-size: 13px; - &:hover { background: rgba(239, 68, 68, 0.08) !important; } + &:hover { background: rgb(239 68 68 / 8%) !important; } } } diff --git a/src/app/organization/employees/create-employee/create-employee.component.scss b/src/app/organization/employees/create-employee/create-employee.component.scss index 220221642..483139010 100644 --- a/src/app/organization/employees/create-employee/create-employee.component.scss +++ b/src/app/organization/employees/create-employee/create-employee.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ce-field { diff --git a/src/app/organization/employees/edit-employee/edit-employee.component.scss b/src/app/organization/employees/edit-employee/edit-employee.component.scss index e5620ee0b..b568bac7a 100644 --- a/src/app/organization/employees/edit-employee/edit-employee.component.scss +++ b/src/app/organization/employees/edit-employee/edit-employee.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ee-field { diff --git a/src/app/organization/employees/view-employee/view-employee.component.scss b/src/app/organization/employees/view-employee/view-employee.component.scss index 4a0e33c4d..b9d49f9ab 100644 --- a/src/app/organization/employees/view-employee/view-employee.component.scss +++ b/src/app/organization/employees/view-employee/view-employee.component.scss @@ -81,14 +81,14 @@ $gray-900: #202124; padding: 20px 28px 24px; background: #fff; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ve-cell { padding: 14px 16px; background: #f8fafc; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); margin: 3px; transition: background 0.12s; @@ -124,12 +124,12 @@ $gray-900: #202124; letter-spacing: 0.2px; &--active { - background: rgba(22, 163, 74, 0.1); + background: rgb(22 163 74 / 10%); color: #16a34a; } &--inactive { - background: rgba(220, 38, 38, 0.1); + background: rgb(220 38 38 / 10%); color: #dc2626; } } diff --git a/src/app/organization/entity-data-table-checks/create-entity-data-table-checks/create-entity-data-table-checks.component.scss b/src/app/organization/entity-data-table-checks/create-entity-data-table-checks/create-entity-data-table-checks.component.scss index 889baceab..b25a627b8 100644 --- a/src/app/organization/entity-data-table-checks/create-entity-data-table-checks/create-entity-data-table-checks.component.scss +++ b/src/app/organization/entity-data-table-checks/create-entity-data-table-checks/create-entity-data-table-checks.component.scss @@ -88,7 +88,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ce-field { diff --git a/src/app/organization/fund-mapping/fund-mapping.component.scss b/src/app/organization/fund-mapping/fund-mapping.component.scss index 77e924257..0fd24ceea 100644 --- a/src/app/organization/fund-mapping/fund-mapping.component.scss +++ b/src/app/organization/fund-mapping/fund-mapping.component.scss @@ -96,7 +96,7 @@ $gray-900: #202124; grid-template-columns: repeat(3, 1fr); } - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; &--3col { grid-template-columns: 1fr; } @@ -127,7 +127,7 @@ $gray-900: #202124; padding: 0 0 8px; margin-top: -4px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } diff --git a/src/app/organization/holidays/create-holiday/create-holiday.component.scss b/src/app/organization/holidays/create-holiday/create-holiday.component.scss index fda995d3f..184580b05 100644 --- a/src/app/organization/holidays/create-holiday/create-holiday.component.scss +++ b/src/app/organization/holidays/create-holiday/create-holiday.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ch-field { diff --git a/src/app/organization/holidays/edit-holiday/edit-holiday.component.scss b/src/app/organization/holidays/edit-holiday/edit-holiday.component.scss index 02c33e574..37152108c 100644 --- a/src/app/organization/holidays/edit-holiday/edit-holiday.component.scss +++ b/src/app/organization/holidays/edit-holiday/edit-holiday.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .eh-field { diff --git a/src/app/organization/holidays/view-holidays/view-holidays.component.scss b/src/app/organization/holidays/view-holidays/view-holidays.component.scss index 1d31294af..3c3735cd2 100644 --- a/src/app/organization/holidays/view-holidays/view-holidays.component.scss +++ b/src/app/organization/holidays/view-holidays/view-holidays.component.scss @@ -85,14 +85,14 @@ $gray-900: #202124; gap: 10px; padding: 20px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vh-cell { padding: 14px 16px; background: $gray-50; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); transition: background 0.12s; &:hover { @@ -130,7 +130,7 @@ $gray-900: #202124; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .vh-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/organization/investors/investors.component.scss b/src/app/organization/investors/investors.component.scss index 42092c64e..41b537318 100644 --- a/src/app/organization/investors/investors.component.scss +++ b/src/app/organization/investors/investors.component.scss @@ -92,8 +92,9 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 768px) { grid-template-columns: 1fr; } - @media (max-width: 1024px) and (min-width: 769px) { grid-template-columns: repeat(2, 1fr); } + @media (width <= 768px) { grid-template-columns: 1fr; } + + @media (width <= 1024px) and (width >= 769px) { grid-template-columns: repeat(2, 1fr); } } .inv-field { @@ -105,7 +106,7 @@ $gray-900: #202124; align-items: center; padding-top: 4px; - @media (max-width: 768px) { padding-top: 0; } + @media (width <= 768px) { padding-top: 0; } } .inv-search-btn { @@ -210,7 +211,7 @@ $gray-900: #202124; gap: 16px; margin-bottom: 16px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } &__item { diff --git a/src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.scss b/src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.scss index 0a959d730..47d4ba65b 100644 --- a/src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.scss +++ b/src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.scss @@ -84,7 +84,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .cl-field { diff --git a/src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.scss b/src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.scss index 63f916f69..99b3e91a5 100644 --- a/src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.scss +++ b/src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.scss @@ -84,7 +84,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .el-field { diff --git a/src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.scss b/src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.scss index cbc811717..465a7912a 100644 --- a/src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.scss +++ b/src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.scss @@ -98,7 +98,7 @@ $gray-900: #202124; gap: 16px; padding: 20px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vl-detail { diff --git a/src/app/organization/loan-provisioning-criteria/create-loan-provisioning-criteria/create-loan-provisioning-criteria.component.scss b/src/app/organization/loan-provisioning-criteria/create-loan-provisioning-criteria/create-loan-provisioning-criteria.component.scss index f7a2673d8..4cd039be9 100644 --- a/src/app/organization/loan-provisioning-criteria/create-loan-provisioning-criteria/create-loan-provisioning-criteria.component.scss +++ b/src/app/organization/loan-provisioning-criteria/create-loan-provisioning-criteria/create-loan-provisioning-criteria.component.scss @@ -94,7 +94,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .cp-field { diff --git a/src/app/organization/loan-provisioning-criteria/edit-loan-provisioning-criteria/edit-loan-provisioning-criteria.component.scss b/src/app/organization/loan-provisioning-criteria/edit-loan-provisioning-criteria/edit-loan-provisioning-criteria.component.scss index 71db39897..4f55d9cfc 100644 --- a/src/app/organization/loan-provisioning-criteria/edit-loan-provisioning-criteria/edit-loan-provisioning-criteria.component.scss +++ b/src/app/organization/loan-provisioning-criteria/edit-loan-provisioning-criteria/edit-loan-provisioning-criteria.component.scss @@ -94,7 +94,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ep-field { diff --git a/src/app/organization/manage-funds/create-fund/create-fund.component.scss b/src/app/organization/manage-funds/create-fund/create-fund.component.scss index d3e1492f4..9e4544806 100644 --- a/src/app/organization/manage-funds/create-fund/create-fund.component.scss +++ b/src/app/organization/manage-funds/create-fund/create-fund.component.scss @@ -84,7 +84,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .cf-field { diff --git a/src/app/organization/manage-funds/edit-fund/edit-fund.component.scss b/src/app/organization/manage-funds/edit-fund/edit-fund.component.scss index 74b701ce6..42c004127 100644 --- a/src/app/organization/manage-funds/edit-fund/edit-fund.component.scss +++ b/src/app/organization/manage-funds/edit-fund/edit-fund.component.scss @@ -84,7 +84,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ef-field { diff --git a/src/app/organization/manage-funds/view-fund/view-fund.component.scss b/src/app/organization/manage-funds/view-fund/view-fund.component.scss index 68d2f178a..13c08bae4 100644 --- a/src/app/organization/manage-funds/view-fund/view-fund.component.scss +++ b/src/app/organization/manage-funds/view-fund/view-fund.component.scss @@ -98,7 +98,7 @@ $gray-900: #202124; gap: 16px; padding: 20px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vf-detail { diff --git a/src/app/organization/offices/create-office/create-office.component.scss b/src/app/organization/offices/create-office/create-office.component.scss index fc39f327e..a6554921b 100644 --- a/src/app/organization/offices/create-office/create-office.component.scss +++ b/src/app/organization/offices/create-office/create-office.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .co-field { diff --git a/src/app/organization/offices/edit-office/edit-office.component.scss b/src/app/organization/offices/edit-office/edit-office.component.scss index f42fefb5c..7523cfeaa 100644 --- a/src/app/organization/offices/edit-office/edit-office.component.scss +++ b/src/app/organization/offices/edit-office/edit-office.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .eo-field { diff --git a/src/app/organization/offices/view-office/general-tab/general-tab.component.scss b/src/app/organization/offices/view-office/general-tab/general-tab.component.scss index c100fed1f..8fee46d85 100644 --- a/src/app/organization/offices/view-office/general-tab/general-tab.component.scss +++ b/src/app/organization/offices/view-office/general-tab/general-tab.component.scss @@ -25,14 +25,14 @@ $gray-900: #202124; grid-template-columns: repeat(2, 1fr); gap: 10px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .gt-cell { padding: 14px 16px; background: $gray-50; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); transition: background 0.12s; &:hover { diff --git a/src/app/organization/offices/view-office/view-office.component.scss b/src/app/organization/offices/view-office/view-office.component.scss index 8935c5007..570c6f71a 100644 --- a/src/app/organization/offices/view-office/view-office.component.scss +++ b/src/app/organization/offices/view-office/view-office.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .vo-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/organization/organization.component.scss b/src/app/organization/organization.component.scss index 11b969490..c6c3aeec8 100644 --- a/src/app/organization/organization.component.scss +++ b/src/app/organization/organization.component.scss @@ -62,7 +62,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -83,7 +83,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; background: $gray-50; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + box-shadow: 0 2px 8px rgb(0 0 0 / 4%); .org-card__arrow { color: $gray-700; @@ -102,62 +102,62 @@ $gray-900: #0f172a; flex-shrink: 0; &--blue { - background: rgba(59, 130, 246, 0.1); + background: rgb(59 130 246 / 10%); color: #2563eb; } &--amber { - background: rgba(245, 158, 11, 0.1); + background: rgb(245 158 11 / 10%); color: #d97706; } &--green { - background: rgba(34, 197, 94, 0.1); + background: rgb(34 197 94 / 10%); color: #16a34a; } &--emerald { - background: rgba(16, 185, 129, 0.1); + background: rgb(16 185 129 / 10%); color: #059669; } &--purple { - background: rgba(139, 92, 246, 0.1); + background: rgb(139 92 246 / 10%); color: #7c3aed; } &--indigo { - background: rgba(99, 102, 241, 0.1); + background: rgb(99 102 241 / 10%); color: #4f46e5; } &--slate { - background: rgba(100, 116, 139, 0.1); + background: rgb(100 116 139 / 10%); color: #475569; } &--orange { - background: rgba(249, 115, 22, 0.1); + background: rgb(249 115 22 / 10%); color: #ea580c; } &--teal { - background: rgba(20, 184, 166, 0.1); + background: rgb(20 184 166 / 10%); color: #0d9488; } &--cyan { - background: rgba(6, 182, 212, 0.1); + background: rgb(6 182 212 / 10%); color: #0891b2; } &--rose { - background: rgba(244, 63, 94, 0.1); + background: rgb(244 63 94 / 10%); color: #e11d48; } &--red { - background: rgba(239, 68, 68, 0.1); + background: rgb(239 68 68 / 10%); color: #dc2626; } } diff --git a/src/app/organization/payment-types/create-payment-type/create-payment-type.component.scss b/src/app/organization/payment-types/create-payment-type/create-payment-type.component.scss index 3788b976a..ae41c8c73 100644 --- a/src/app/organization/payment-types/create-payment-type/create-payment-type.component.scss +++ b/src/app/organization/payment-types/create-payment-type/create-payment-type.component.scss @@ -88,7 +88,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .cp-field { diff --git a/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.scss b/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.scss index cd6f9eb7d..509e7e89d 100644 --- a/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.scss +++ b/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.scss @@ -88,7 +88,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .ep-field { diff --git a/src/app/organization/standing-instructions-history/standing-instructions-history.component.scss b/src/app/organization/standing-instructions-history/standing-instructions-history.component.scss index 2d26ade30..c246b4d09 100644 --- a/src/app/organization/standing-instructions-history/standing-instructions-history.component.scss +++ b/src/app/organization/standing-instructions-history/standing-instructions-history.component.scss @@ -59,7 +59,7 @@ $gray-900: #202124; border: 1px solid $border; border-radius: 12px; padding: 0; - box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04); + box-shadow: 0 1px 3px rgb(16 24 40 / 6%), 0 1px 2px rgb(16 24 40 / 4%); } .si-section { @@ -91,7 +91,7 @@ $gray-900: #202124; grid-template-columns: 1fr 1fr; gap: 4px 20px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -129,7 +129,7 @@ $gray-900: #202124; font-weight: 500; font-size: 13px; padding: 4px 20px !important; - box-shadow: 0 1px 2px rgba(19, 83, 160, 0.15); + box-shadow: 0 1px 2px rgb(19 83 160 / 15%); transition: all 0.2s ease; i { @@ -139,7 +139,7 @@ $gray-900: #202124; &:hover { background: #0f4688 !important; - box-shadow: 0 2px 6px rgba(19, 83, 160, 0.25); + box-shadow: 0 2px 6px rgb(19 83 160 / 25%); } } } @@ -150,7 +150,7 @@ $gray-900: #202124; border: 1px solid $border; border-radius: 12px; overflow: hidden; - box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04); + box-shadow: 0 1px 3px rgb(16 24 40 / 6%), 0 1px 2px rgb(16 24 40 / 4%); } .modern-table { diff --git a/src/app/organization/tellers/cashiers/allocate-cash/allocate-cash.component.scss b/src/app/organization/tellers/cashiers/allocate-cash/allocate-cash.component.scss index 3e265191e..824ab4073 100644 --- a/src/app/organization/tellers/cashiers/allocate-cash/allocate-cash.component.scss +++ b/src/app/organization/tellers/cashiers/allocate-cash/allocate-cash.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .ac-field { diff --git a/src/app/organization/tellers/cashiers/create-cashier/create-cashier.component.scss b/src/app/organization/tellers/cashiers/create-cashier/create-cashier.component.scss index a2ed21202..f9d60f713 100644 --- a/src/app/organization/tellers/cashiers/create-cashier/create-cashier.component.scss +++ b/src/app/organization/tellers/cashiers/create-cashier/create-cashier.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .cc-field { @@ -107,7 +107,7 @@ $gray-900: #202124; display: flex !important; gap: 12px; - @media (max-width: 600px) { flex-direction: column; gap: 4px; } + @media (width <= 600px) { flex-direction: column; gap: 4px; } } .cc-time-field { diff --git a/src/app/organization/tellers/cashiers/edit-cashier/edit-cashier.component.scss b/src/app/organization/tellers/cashiers/edit-cashier/edit-cashier.component.scss index 277c44fe8..8ceb7c57d 100644 --- a/src/app/organization/tellers/cashiers/edit-cashier/edit-cashier.component.scss +++ b/src/app/organization/tellers/cashiers/edit-cashier/edit-cashier.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .ec-field { @@ -107,7 +107,7 @@ $gray-900: #202124; display: flex !important; gap: 12px; - @media (max-width: 600px) { flex-direction: column; gap: 4px; } + @media (width <= 600px) { flex-direction: column; gap: 4px; } } .ec-time-field { diff --git a/src/app/organization/tellers/cashiers/settle-cash/settle-cash.component.scss b/src/app/organization/tellers/cashiers/settle-cash/settle-cash.component.scss index b15f002e9..6b1403ba9 100644 --- a/src/app/organization/tellers/cashiers/settle-cash/settle-cash.component.scss +++ b/src/app/organization/tellers/cashiers/settle-cash/settle-cash.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .sc-field { diff --git a/src/app/organization/tellers/cashiers/transactions/transactions.component.scss b/src/app/organization/tellers/cashiers/transactions/transactions.component.scss index 8952a97de..5188b6e53 100644 --- a/src/app/organization/tellers/cashiers/transactions/transactions.component.scss +++ b/src/app/organization/tellers/cashiers/transactions/transactions.component.scss @@ -27,7 +27,7 @@ $gray-900: #202124; border-radius: 10px; margin-bottom: 16px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: repeat(2, 1fr); } diff --git a/src/app/organization/tellers/cashiers/view-cashier/view-cashier.component.scss b/src/app/organization/tellers/cashiers/view-cashier/view-cashier.component.scss index e945e341d..b2a5565d1 100644 --- a/src/app/organization/tellers/cashiers/view-cashier/view-cashier.component.scss +++ b/src/app/organization/tellers/cashiers/view-cashier/view-cashier.component.scss @@ -112,7 +112,7 @@ $gray-900: #202124; gap: 16px; padding: 20px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr !important; } + @media (width <= 600px) { grid-template-columns: 1fr !important; } } .vc-detail { diff --git a/src/app/organization/tellers/create-teller/create-teller.component.scss b/src/app/organization/tellers/create-teller/create-teller.component.scss index 8313d5bc3..04d0f5b8b 100644 --- a/src/app/organization/tellers/create-teller/create-teller.component.scss +++ b/src/app/organization/tellers/create-teller/create-teller.component.scss @@ -84,7 +84,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ct-field { diff --git a/src/app/organization/tellers/edit-teller/edit-teller.component.scss b/src/app/organization/tellers/edit-teller/edit-teller.component.scss index 2ae5f1468..d6915bea8 100644 --- a/src/app/organization/tellers/edit-teller/edit-teller.component.scss +++ b/src/app/organization/tellers/edit-teller/edit-teller.component.scss @@ -84,7 +84,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .et-field { diff --git a/src/app/organization/tellers/view-teller/view-teller.component.scss b/src/app/organization/tellers/view-teller/view-teller.component.scss index 1017eea04..5a02e993d 100644 --- a/src/app/organization/tellers/view-teller/view-teller.component.scss +++ b/src/app/organization/tellers/view-teller/view-teller.component.scss @@ -108,7 +108,7 @@ $gray-900: #202124; gap: 16px; padding: 20px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vt-detail { diff --git a/src/app/products/charges/create-charge/create-charge.component.scss b/src/app/products/charges/create-charge/create-charge.component.scss index cc1d898a4..d48d0ed63 100644 --- a/src/app/products/charges/create-charge/create-charge.component.scss +++ b/src/app/products/charges/create-charge/create-charge.component.scss @@ -98,10 +98,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .cc-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/charges/edit-charge/edit-charge.component.scss b/src/app/products/charges/edit-charge/edit-charge.component.scss index 8263ebc96..3126303d6 100644 --- a/src/app/products/charges/edit-charge/edit-charge.component.scss +++ b/src/app/products/charges/edit-charge/edit-charge.component.scss @@ -98,10 +98,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .ec-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/charges/view-charge/view-charge.component.scss b/src/app/products/charges/view-charge/view-charge.component.scss index 07e5ecc49..fe770b1db 100644 --- a/src/app/products/charges/view-charge/view-charge.component.scss +++ b/src/app/products/charges/view-charge/view-charge.component.scss @@ -109,10 +109,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .vc-top-bar { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/collaterals/create-collateral/create-collateral.component.scss b/src/app/products/collaterals/create-collateral/create-collateral.component.scss index 8943f0478..627a547d4 100644 --- a/src/app/products/collaterals/create-collateral/create-collateral.component.scss +++ b/src/app/products/collaterals/create-collateral/create-collateral.component.scss @@ -72,10 +72,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .ccl-section__body { grid-template-columns: 1fr; } diff --git a/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss b/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss index cfd828fc7..e46a033ad 100644 --- a/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss +++ b/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss @@ -72,10 +72,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .ecl-section__body { grid-template-columns: 1fr; } diff --git a/src/app/products/collaterals/view-collateral/view-collateral.component.scss b/src/app/products/collaterals/view-collateral/view-collateral.component.scss index b99904792..2ea21f890 100644 --- a/src/app/products/collaterals/view-collateral/view-collateral.component.scss +++ b/src/app/products/collaterals/view-collateral/view-collateral.component.scss @@ -99,7 +99,7 @@ } } -@media (max-width: 600px) { +@media (width <= 600px) { .vcl-top-bar { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/current-products/create-current-product/create-current-product.component.scss b/src/app/products/current-products/create-current-product/create-current-product.component.scss index 946837366..98f8a7bee 100644 --- a/src/app/products/current-products/create-current-product/create-current-product.component.scss +++ b/src/app/products/current-products/create-current-product/create-current-product.component.scss @@ -35,11 +35,11 @@ $primary: #1353a0; .cp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -77,10 +77,10 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); &__cancel { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &__right { diff --git a/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss b/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss index 38cce13dd..8e2ab2292 100644 --- a/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss +++ b/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss @@ -18,9 +18,10 @@ $primary: #1353a0; .fdp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); + ::ng-deep { - .mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); } + .mat-mdc-tab-header { border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; } .mat-mdc-tab-body-wrapper { min-height: 200px; } .mat-mdc-tab-body { overflow: visible !important; } @@ -33,7 +34,7 @@ $primary: #1353a0; .fdp-action-bar { display: flex; justify-content: space-between; align-items: center; - margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08); - &__cancel { color: rgba(0, 0, 0, 0.6); } + margin-top: 20px; padding: 16px 0; border-top: 1px solid rgb(0 0 0 / 8%); + &__cancel { color: rgb(0 0 0 / 60%); } &__right { display: flex; gap: 12px; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss index 2f9db0ace..74f18ffcf 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -62,7 +62,7 @@ gap: 24px; flex-wrap: wrap; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss index 1e57eef92..ccfe94921 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss index a6baa2b82..fe1c47dc5 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss index c561e87f5..7747019fc 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss index 161bdd25c..e74afdae2 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss @@ -51,7 +51,7 @@ grid-template-columns: repeat(2, 1fr); gap: 4px 16px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss index 5e4dbc4ec..4ae6c504a 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss @@ -57,7 +57,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss index 41cc7b11f..62b1187f8 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss index 49fd6b91b..ac8a2ddd5 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss b/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss index e5da63228..9a9eda976 100644 --- a/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss +++ b/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss @@ -152,7 +152,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .cfr-container { padding: 16px 10px; } diff --git a/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss b/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss index 4cf972d91..2c1864327 100644 --- a/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss +++ b/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss @@ -152,7 +152,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .efr-container { padding: 16px 10px; } diff --git a/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss b/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss index 3c5fe96f7..9fc868d02 100644 --- a/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss +++ b/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss @@ -154,7 +154,7 @@ margin-right: 10px; } -@media (max-width: 600px) { +@media (width <= 600px) { .vfr-container { padding: 16px 10px; } diff --git a/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss b/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss index 65cff69e8..d05cedb1a 100644 --- a/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss +++ b/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss @@ -35,11 +35,11 @@ $primary: #1353a0; .lp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -77,10 +77,10 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); &__cancel { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &__right { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss index de058a346..687d9456d 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss @@ -112,7 +112,7 @@ } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .lpa-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss index 1c455c6f9..3b4fc2e89 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss @@ -39,7 +39,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpdi-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss index e9667a432..a9cfa9a1f 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss @@ -57,7 +57,7 @@ $gray-900: #202124; box-shadow: 0 1px 3px rgba($primary, 0.3) !important; } - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; &__select { max-width: 100%; width: 100%; } } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss index eb806e2d3..5dbbf06b8 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss @@ -39,7 +39,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpc-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss index f50bafc78..01e69962d 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss @@ -39,7 +39,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpd-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss index 5d6f7a21b..c8dc9d8b9 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss @@ -37,7 +37,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpir-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss index 9fbe0d1ec..ef42e0e74 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss @@ -53,7 +53,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -80,7 +80,7 @@ grid-template-columns: repeat(2, 1fr); gap: 4px 16px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss index 883ed9f1c..5f40cb5b5 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss @@ -50,7 +50,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -58,7 +58,7 @@ .lpt-grid-3 { grid-template-columns: repeat(3, 1fr); - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -66,11 +66,11 @@ .lpt-grid-4 { grid-template-columns: repeat(4, 1fr); - @media (max-width: 900px) { + @media (width <= 900px) { grid-template-columns: repeat(2, 1fr); } - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss index d043d9455..4ba22656a 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .db-field { diff --git a/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss index d043d9455..4ba22656a 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .db-field { diff --git a/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss index baee4a424..c191f95f5 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss @@ -31,9 +31,9 @@ .cr-action-bar { display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .cr-grid { grid-template-columns: 1fr; } } diff --git a/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss index e5a3364e0..53d95280c 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss @@ -31,9 +31,9 @@ .er-action-bar { display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .er-grid { grid-template-columns: 1fr; } } diff --git a/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss b/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss index 8d62191ed..cc835be50 100644 --- a/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss +++ b/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss @@ -23,7 +23,7 @@ &:hover { border-color: #0ea5e9; - box-shadow: 0 2px 8px rgba(14, 165, 233, 0.12); + box-shadow: 0 2px 8px rgb(14 165 233 / 12%); } &__icon-wrap { diff --git a/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss b/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss index 54e7899d4..ddf54f2c6 100644 --- a/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss +++ b/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ctc-field { diff --git a/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss b/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss index 57173690c..4eddbd8f5 100644 --- a/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss +++ b/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .etc-field { diff --git a/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss b/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss index 83d5d9403..5d3396259 100644 --- a/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss +++ b/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss @@ -81,14 +81,14 @@ $gray-900: #202124; padding: 20px 28px 24px; background: #fff; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vtc-cell { padding: 14px 16px; background: #f8fafc; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); margin: 3px; transition: background 0.12s; @@ -114,7 +114,7 @@ $gray-900: #202124; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .vtc-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss b/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss index bc087f023..b107dad99 100644 --- a/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss +++ b/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss @@ -62,7 +62,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -82,7 +82,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + box-shadow: 0 2px 8px rgb(0 0 0 / 6%); transform: translateY(-1px); } diff --git a/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss b/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss index 5fc7c9d76..38a8f6a0a 100644 --- a/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss +++ b/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss @@ -102,7 +102,7 @@ $text-muted: #64748b; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ctg-field { diff --git a/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss b/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss index 508e65c38..5558ddbaa 100644 --- a/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss +++ b/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss @@ -102,7 +102,7 @@ $text-muted: #64748b; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .etg-field { diff --git a/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss b/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss index e17847cf5..107152abd 100644 --- a/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss +++ b/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss @@ -109,14 +109,14 @@ $text-muted: #64748b; gap: 1px; padding: 20px 28px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vtg-cell { padding: 14px 16px; background: #f8fafc; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); margin: 3px; transition: background 0.12s; @@ -195,7 +195,7 @@ $text-muted: #64748b; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .vtg-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss b/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss index 621e88022..a7e748e41 100644 --- a/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss +++ b/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss @@ -81,7 +81,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .cpm-container { padding: 16px 12px; } diff --git a/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss b/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss index 2a0403cdf..c8f348e5d 100644 --- a/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss +++ b/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss @@ -81,7 +81,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .epm-container { padding: 16px 12px; } diff --git a/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss b/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss index 069352a61..7ed4e4fb4 100644 --- a/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss +++ b/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss @@ -156,7 +156,7 @@ background: transparent; } -@media (max-width: 600px) { +@media (width <= 600px) { .vpm-container { padding: 16px 12px; } diff --git a/src/app/products/products.component.scss b/src/app/products/products.component.scss index 985812ad5..698497b5f 100644 --- a/src/app/products/products.component.scss +++ b/src/app/products/products.component.scss @@ -61,7 +61,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -82,7 +82,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; background: $gray-50; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + box-shadow: 0 2px 8px rgb(0 0 0 / 4%); .pd-card__arrow { color: $gray-700; @@ -101,62 +101,62 @@ $gray-900: #0f172a; flex-shrink: 0; &--blue { - background: rgba(59, 130, 246, 0.1); + background: rgb(59 130 246 / 10%); color: #2563eb; } &--green { - background: rgba(34, 197, 94, 0.1); + background: rgb(34 197 94 / 10%); color: #16a34a; } &--cyan { - background: rgba(6, 182, 212, 0.1); + background: rgb(6 182 212 / 10%); color: #0891b2; } &--purple { - background: rgba(139, 92, 246, 0.1); + background: rgb(139 92 246 / 10%); color: #7c3aed; } &--amber { - background: rgba(245, 158, 11, 0.1); + background: rgb(245 158 11 / 10%); color: #d97706; } &--teal { - background: rgba(20, 184, 166, 0.1); + background: rgb(20 184 166 / 10%); color: #0d9488; } &--indigo { - background: rgba(99, 102, 241, 0.1); + background: rgb(99 102 241 / 10%); color: #4f46e5; } &--orange { - background: rgba(249, 115, 22, 0.1); + background: rgb(249 115 22 / 10%); color: #ea580c; } &--slate { - background: rgba(100, 116, 139, 0.1); + background: rgb(100 116 139 / 10%); color: #475569; } &--rose { - background: rgba(244, 63, 94, 0.1); + background: rgb(244 63 94 / 10%); color: #e11d48; } &--emerald { - background: rgba(16, 185, 129, 0.1); + background: rgb(16 185 129 / 10%); color: #059669; } &--red { - background: rgba(239, 68, 68, 0.1); + background: rgb(239 68 68 / 10%); color: #dc2626; } } diff --git a/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss b/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss index 6d2fa4996..67ab3211c 100644 --- a/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss +++ b/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss @@ -18,9 +18,10 @@ $primary: #1353a0; .rdp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); + ::ng-deep { - .mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); } + .mat-mdc-tab-header { border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; } .mat-mdc-tab-body-wrapper { min-height: 200px; } .mat-mdc-tab-body { overflow: visible !important; } @@ -33,7 +34,7 @@ $primary: #1353a0; .rdp-action-bar { display: flex; justify-content: space-between; align-items: center; - margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08); - &__cancel { color: rgba(0, 0, 0, 0.6); } + margin-top: 20px; padding: 16px 0; border-top: 1px solid rgb(0 0 0 / 8%); + &__cancel { color: rgb(0 0 0 / 60%); } &__right { display: flex; gap: 12px; } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss index 250653495..dd5e8e63f 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss @@ -73,7 +73,7 @@ table { width: 100%; } } -@media (max-width: 600px) { +@media (width <= 600px) { .rdpa-grid { grid-template-columns: 1fr; } .rdpa-radio-group { flex-direction: column; } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss index c0b63cb17..b223cc024 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss @@ -59,7 +59,7 @@ padding: 4px 0; } -@media (max-width: 600px) { +@media (width <= 600px) { .rdpc-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss index 543151363..4e3d3848e 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss @@ -53,7 +53,7 @@ } } -@media (max-width: 600px) { +@media (width <= 600px) { .rdpd-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss index 09b447b0c..e72e64e10 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss @@ -143,7 +143,7 @@ margin-bottom: 12px; } -@media (max-width: 600px) { +@media (width <= 600px) { .rdpi-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss index d94100dfe..c4670dd8f 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss @@ -59,7 +59,7 @@ padding: 4px 0; } -@media (max-width: 600px) { +@media (width <= 600px) { .rdps-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss index a520130f8..4fcbd4861 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss @@ -57,7 +57,7 @@ } } -@media (max-width: 600px) { +@media (width <= 600px) { .rdpt-grid { grid-template-columns: 1fr; diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss b/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss index 8bfaacb7f..7a61d6712 100644 --- a/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss @@ -35,11 +35,11 @@ $primary: #1353a0; .sp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -77,10 +77,10 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); &__cancel { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &__right { diff --git a/src/app/products/saving-products/edit-saving-product/edit-saving-product.component.scss b/src/app/products/saving-products/edit-saving-product/edit-saving-product.component.scss index 8bfaacb7f..7a61d6712 100644 --- a/src/app/products/saving-products/edit-saving-product/edit-saving-product.component.scss +++ b/src/app/products/saving-products/edit-saving-product/edit-saving-product.component.scss @@ -35,11 +35,11 @@ $primary: #1353a0; .sp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -77,10 +77,10 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); &__cancel { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &__right { diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss index d9652bdb9..02908020d 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss @@ -57,7 +57,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -75,7 +75,7 @@ $gray-900: #202124; gap: 24px; flex-wrap: wrap; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; gap: 12px; } diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss index 33519f366..f272b71f6 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss @@ -65,7 +65,7 @@ $gray-900: #202124; box-shadow: 0 1px 3px rgba($primary, 0.3) !important; } - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; &__select { diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss index c2d90f817..eaf1489cd 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss @@ -50,7 +50,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss index 343ad6b21..713bdab40 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss @@ -46,7 +46,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss index 0d8bb861f..d41d873e1 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss @@ -50,7 +50,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss index 10bb836ea..9f31e6a64 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss @@ -50,7 +50,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/share-products/create-share-product/create-share-product.component.scss b/src/app/products/share-products/create-share-product/create-share-product.component.scss index 95c13fd11..6f7b6c969 100644 --- a/src/app/products/share-products/create-share-product/create-share-product.component.scss +++ b/src/app/products/share-products/create-share-product/create-share-product.component.scss @@ -27,10 +27,10 @@ $primary: #1353a0; .shp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { - .mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); } + .mat-mdc-tab-header { border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; } .mat-mdc-tab-body-wrapper { min-height: 200px; } .mat-mdc-tab-body { overflow: visible !important; } @@ -47,8 +47,8 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); - &__cancel { color: rgba(0, 0, 0, 0.6); } + &__cancel { color: rgb(0 0 0 / 60%); } &__right { display: flex; gap: 12px; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss index 17f1d4ab4..fa8e0ab44 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss index e055abfed..b1b3a5d6c 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss @@ -44,7 +44,7 @@ gap: 16px; margin-bottom: 16px; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; align-items: stretch; } @@ -54,7 +54,7 @@ flex: 1; max-width: 400px; - @media (max-width: 600px) { + @media (width <= 600px) { max-width: 100%; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss index 4418949a8..2b4edfb6d 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss @@ -31,7 +31,8 @@ $gray-900: #202124; .shpcr-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .shpcr-field { diff --git a/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss index b521d4162..a8e05a519 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss @@ -29,7 +29,8 @@ $gray-900: #202124; .shpd-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .shpd-field { diff --git a/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss index e0921d1c9..427ce59ca 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss @@ -61,7 +61,7 @@ font-size: 13px; line-height: 1.5; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss index 5122b92b7..5b661cd01 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss index f5c638d7c..34b0f20d8 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss @@ -29,7 +29,8 @@ $gray-900: #202124; .shpt-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .shpt-field { diff --git a/src/app/reports/reports.component.scss b/src/app/reports/reports.component.scss index 2f3d85fd3..d88df72ce 100644 --- a/src/app/reports/reports.component.scss +++ b/src/app/reports/reports.component.scss @@ -107,8 +107,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; grid-template-columns: repeat(3, 1fr); gap: 12px; - @media (max-width: 900px) { grid-template-columns: repeat(2, 1fr); } - @media (max-width: 500px) { grid-template-columns: 1fr; } + @media (width <= 900px) { grid-template-columns: repeat(2, 1fr); } + + @media (width <= 500px) { grid-template-columns: 1fr; } } .rpt-tile { @@ -117,13 +118,13 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; gap: 14px; padding: 16px 18px; background: #fff; - border: 1px solid rgba(0, 0, 0, 0.06); + border: 1px solid rgb(0 0 0 / 6%); border-radius: 8px; cursor: pointer; transition: box-shadow 0.15s, transform 0.15s; &:hover { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + box-shadow: 0 4px 16px rgb(0 0 0 / 8%); transform: translateY(-1px); } @@ -177,7 +178,7 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; .rpt-tiles--sub { grid-template-columns: repeat(2, 1fr); - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } // Detail view @@ -294,7 +295,7 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; :host-context(.dark-theme) { .rpt-tile { background: #1e1e2e; - border-color: rgba(255, 255, 255, 0.06); + border-color: rgb(255 255 255 / 6%); } .rpt-tile__name { color: #e0e0e0; } diff --git a/src/app/shared/footer/footer.component.scss b/src/app/shared/footer/footer.component.scss index 8fe159925..a48a8cb09 100644 --- a/src/app/shared/footer/footer.component.scss +++ b/src/app/shared/footer/footer.component.scss @@ -62,8 +62,8 @@ $accent-2: #10b981; flex-direction: column; padding: 3px 40px; background: #f8f9fa; - border-top: 1px solid rgba(0, 0, 0, 0.06); - border-bottom: 1px solid rgba(0, 0, 0, 0.06); + border-top: 1px solid rgb(0 0 0 / 6%); + border-bottom: 1px solid rgb(0 0 0 / 6%); margin-top: 0; } @@ -91,7 +91,7 @@ $accent-2: #10b981; align-items: center; gap: 10px; padding-right: 20px; - border-right: 1px solid rgba(0, 0, 0, 0.1); + border-right: 1px solid rgb(0 0 0 / 10%); margin-right: 0; } @@ -116,7 +116,7 @@ $accent-2: #10b981; .sec-brand-delimiter { font-size: 12px; - color: rgba(0, 0, 0, 0.2); + color: rgb(0 0 0 / 20%); margin: 0 6px; font-weight: 300; } @@ -200,7 +200,7 @@ $accent-2: #10b981; .ls-label { font-size: 11px; font-weight: 700; - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); text-transform: uppercase; letter-spacing: 0.8px; white-space: nowrap; @@ -222,34 +222,34 @@ $accent-2: #10b981; white-space: nowrap; &.green { - background: rgba(16, 185, 129, 0.15); + background: rgb(16 185 129 / 15%); color: #6ee7b7; - border: 1px solid rgba(16, 185, 129, 0.3); + border: 1px solid rgb(16 185 129 / 30%); } &.blue { - background: rgba(14, 165, 233, 0.15); + background: rgb(14 165 233 / 15%); color: #7dd3fc; - border: 1px solid rgba(14, 165, 233, 0.3); + border: 1px solid rgb(14 165 233 / 30%); } &.gold { - background: rgba(245, 158, 11, 0.15); + background: rgb(245 158 11 / 15%); color: #fcd34d; - border: 1px solid rgba(245, 158, 11, 0.3); + border: 1px solid rgb(245 158 11 / 30%); } &.white { - background: rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.75); - border: 1px solid rgba(255, 255, 255, 0.15); + background: rgb(255 255 255 / 8%); + color: rgb(255 255 255 / 75%); + border: 1px solid rgb(255 255 255 / 15%); } } // ===== Default Mode (Shell Footer) ===== .app-footer { background: $brand-dark; - color: rgba(255, 255, 255, 0.85); + color: rgb(255 255 255 / 85%); padding: 0; margin-top: 0; position: relative; @@ -258,10 +258,7 @@ $accent-2: #10b981; &::after { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background: repeating-linear-gradient( 108deg, transparent 0, transparent 80px, rgb(255 255 255 / 2.2%) 80px, rgb(255 255 255 / 2.2%) 160px @@ -323,7 +320,7 @@ $accent-2: #10b981; .footer-tagline { font-size: 12px; - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); line-height: 1.5; margin: 0; } @@ -355,13 +352,13 @@ $accent-2: #10b981; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); margin: 0 0 2px; } .footer-link { font-size: 12px; - color: rgba(255, 255, 255, 0.75); + color: rgb(255 255 255 / 75%); text-decoration: none; transition: color 0.15s; line-height: 1.5; @@ -383,7 +380,7 @@ $accent-2: #10b981; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); margin: 0 0 2px; } @@ -398,13 +395,13 @@ $accent-2: #10b981; align-items: center; gap: 6px; padding: 4px 10px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + background: rgb(255 255 255 / 4%); + border: 1px solid rgb(255 255 255 / 8%); border-radius: 5px; transition: background 0.15s; &:hover { - background: rgba(255, 255, 255, 0.08); + background: rgb(255 255 255 / 8%); } } @@ -421,13 +418,13 @@ $accent-2: #10b981; .reg-label { font-size: 11px; - color: rgba(255, 255, 255, 0.75); + color: rgb(255 255 255 / 75%); font-weight: 500; } .reg-link { font-size: 11px; - color: rgba(255, 255, 255, 0.75); + color: rgb(255 255 255 / 75%); text-decoration: none; font-weight: 500; transition: color 0.15s; @@ -447,21 +444,21 @@ $accent-2: #10b981; flex-shrink: 0; &.rbi { - background: rgba(14, 165, 233, 0.2); + background: rgb(14 165 233 / 20%); color: #7dd3fc; - border: 1px solid rgba(14, 165, 233, 0.3); + border: 1px solid rgb(14 165 233 / 30%); } &.circular { - background: rgba(245, 158, 11, 0.15); + background: rgb(245 158 11 / 15%); color: #fcd34d; - border: 1px solid rgba(245, 158, 11, 0.3); + border: 1px solid rgb(245 158 11 / 30%); } &.mandatory { - background: rgba(239, 68, 68, 0.15); + background: rgb(239 68 68 / 15%); color: #fca5a5; - border: 1px solid rgba(239, 68, 68, 0.3); + border: 1px solid rgb(239 68 68 / 30%); } } @@ -477,7 +474,7 @@ $accent-2: #10b981; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); margin: 0 0 4px; } @@ -492,14 +489,14 @@ $accent-2: #10b981; align-items: center; gap: 6px; padding: 4px 12px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + background: rgb(255 255 255 / 4%); + border: 1px solid rgb(255 255 255 / 8%); border-radius: 6px; text-decoration: none; transition: background 0.15s; &:hover { - background: rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 10%); .qn-label { color: #5ec9f8; @@ -515,7 +512,7 @@ $accent-2: #10b981; .qn-label { font-size: 12px; - color: rgba(255, 255, 255, 0.8); + color: rgb(255 255 255 / 80%); font-weight: 500; transition: color 0.15s; } @@ -527,9 +524,9 @@ $accent-2: #10b981; border-radius: 3px; letter-spacing: 0.5px; text-transform: uppercase; - background: rgba(14, 165, 233, 0.2); + background: rgb(14 165 233 / 20%); color: #7dd3fc; - border: 1px solid rgba(14, 165, 233, 0.3); + border: 1px solid rgb(14 165 233 / 30%); flex-shrink: 0; } @@ -545,7 +542,7 @@ $accent-2: #10b981; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); margin: 0 0 4px; } @@ -561,14 +558,14 @@ $accent-2: #10b981; align-items: center; gap: 6px; padding: 5px 12px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + background: rgb(255 255 255 / 4%); + border: 1px solid rgb(255 255 255 / 8%); border-radius: 5px; transition: background 0.15s; white-space: nowrap; &:hover { - background: rgba(255, 255, 255, 0.08); + background: rgb(255 255 255 / 8%); } &.sc-chat { @@ -594,13 +591,13 @@ $accent-2: #10b981; .sc-label { font-size: 10px; - color: rgba(255, 255, 255, 0.45); + color: rgb(255 255 255 / 45%); font-weight: 500; } .sc-val { font-size: 11px; - color: rgba(255, 255, 255, 0.85); + color: rgb(255 255 255 / 85%); font-weight: 600; &.highlight { @@ -611,7 +608,7 @@ $accent-2: #10b981; // ── Divider ── .footer-divider { height: 1px; - background: rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 10%); margin: 0 40px; position: relative; z-index: 1; @@ -630,7 +627,7 @@ $accent-2: #10b981; .fb-copy { font-size: 11px; - color: rgba(255, 255, 255, 0.4); + color: rgb(255 255 255 / 40%); white-space: nowrap; } @@ -641,7 +638,7 @@ $accent-2: #10b981; a { font-size: 11px; - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); text-decoration: none; padding: 2px 6px; border-radius: 3px; @@ -649,7 +646,7 @@ $accent-2: #10b981; &:hover { color: #5ec9f8; - background: rgba(255, 255, 255, 0.06); + background: rgb(255 255 255 / 6%); } } } @@ -659,18 +656,18 @@ $accent-2: #10b981; align-items: center; gap: 4px; font-size: 10px; - color: rgba(255, 255, 255, 0.35); + color: rgb(255 255 255 / 35%); margin-left: auto; strong { - color: rgba(255, 255, 255, 0.55); + color: rgb(255 255 255 / 55%); font-weight: 600; } } .fb-powered { font-size: 10px; - color: rgba(255, 255, 255, 0.35); + color: rgb(255 255 255 / 35%); strong { color: #5ec9f8; @@ -679,7 +676,7 @@ $accent-2: #10b981; } // ===== Responsive ===== -@media (max-width: 768px) { +@media (width <= 768px) { .security-bar { flex-direction: column; gap: 8px; @@ -747,7 +744,7 @@ $accent-2: #10b981; } } -@media (max-width: 480px) { +@media (width <= 480px) { .footer-compact { font-size: 0.7rem; diff --git a/src/app/shared/form-dialog/form-dialog.component.scss b/src/app/shared/form-dialog/form-dialog.component.scss index 5fa85ce2b..31ecb3638 100644 --- a/src/app/shared/form-dialog/form-dialog.component.scss +++ b/src/app/shared/form-dialog/form-dialog.component.scss @@ -175,7 +175,7 @@ $white: #fff; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .fd-grid--cols-2, .fd-grid--cols-3 { grid-template-columns: 1fr; diff --git a/src/app/shared/icons.module.ts b/src/app/shared/icons.module.ts index 1e9cd3f9c..32acaeb75 100644 --- a/src/app/shared/icons.module.ts +++ b/src/app/shared/icons.module.ts @@ -173,7 +173,22 @@ import { faCalendarTimes, faStar, faCrop, - faStamp + faStamp, + faSpinner, + faPhone, + faHashtag, + faMapMarkerAlt, + faHourglassHalf, + faCheckSquare, + faCheckDouble, + faClipboardCheck, + faFileArchive, + faSyncAlt, + faIdCardAlt, + faCube, + faLongArrowAltRight, + faStream, + faThLarge } from '@fortawesome/free-solid-svg-icons'; /** @@ -353,7 +368,22 @@ export class IconsModule { faCalendarTimes, faStar, faCrop, - faStamp + faStamp, + faSpinner, + faPhone, + faHashtag, + faMapMarkerAlt, + faHourglassHalf, + faCheckSquare, + faCheckDouble, + faClipboardCheck, + faFileArchive, + faSyncAlt, + faIdCardAlt, + faCube, + faLongArrowAltRight, + faStream, + faThLarge ); } } diff --git a/src/app/shared/styles/list-layout.scss b/src/app/shared/styles/list-layout.scss index 8af67d841..b16a422cf 100644 --- a/src/app/shared/styles/list-layout.scss +++ b/src/app/shared/styles/list-layout.scss @@ -229,6 +229,60 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; flex-wrap: wrap; } +.table-header-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.table-filters { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + padding: 10px 12px; + background: #f8fafc; + border: 1px solid $border; + border-radius: 10px; + + .table-search { + margin: 0; + } +} + +.table-filter { + min-width: 150px; + max-width: 180px; + margin: 0; + + ::ng-deep { + .mat-mdc-form-field-flex { + height: 38px; + background: #fff; + } + + .mat-mdc-form-field-infix { + padding: 6px 0; + min-height: 38px; + } + + .mdc-notched-outline__leading { + border-radius: 8px 0 0 8px; + } + + .mdc-notched-outline__trailing { + border-radius: 0 8px 8px 0; + } + + .mat-mdc-select-value, + .mat-mdc-select-placeholder { + font-size: 13px; + } + } +} + .table-search { flex: 1; min-width: 200px; @@ -268,6 +322,8 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; .table-checkbox { flex-shrink: 0; font-size: 13px; + margin-left: auto; + white-space: nowrap; } .table-buttons { @@ -484,6 +540,15 @@ $mono: 'Roboto Mono', 'DM Mono', monospace; } } +.row-ckyc-icon { + font-size: 16px; + width: 16px; + height: 16px; + vertical-align: middle; + margin-left: 6px; + color: #16a34a; +} + .legal-form-badge { display: inline-flex; align-items: center; diff --git a/src/app/system/audit-trails/audit-trails.component.scss b/src/app/system/audit-trails/audit-trails.component.scss index 3554b8d59..2c7b55719 100644 --- a/src/app/system/audit-trails/audit-trails.component.scss +++ b/src/app/system/audit-trails/audit-trails.component.scss @@ -263,7 +263,7 @@ $muted: #64748b; } // ===== Responsive ===== -@media (max-width: 960px) { +@media (width <= 960px) { .at-filters__row--3 { grid-template-columns: repeat(2, 1fr); } @@ -278,7 +278,7 @@ $muted: #64748b; } } -@media (max-width: 600px) { +@media (width <= 600px) { .at-filters__row--3 { grid-template-columns: 1fr; } diff --git a/src/app/system/codes/create-code/create-code.component.scss b/src/app/system/codes/create-code/create-code.component.scss index 78e826641..1e650846b 100644 --- a/src/app/system/codes/create-code/create-code.component.scss +++ b/src/app/system/codes/create-code/create-code.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .cc-field { diff --git a/src/app/system/codes/edit-code/edit-code.component.scss b/src/app/system/codes/edit-code/edit-code.component.scss index 71c7cef10..681177af7 100644 --- a/src/app/system/codes/edit-code/edit-code.component.scss +++ b/src/app/system/codes/edit-code/edit-code.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ec-field { diff --git a/src/app/system/codes/view-code/view-code.component.scss b/src/app/system/codes/view-code/view-code.component.scss index 4b206c4df..9e71b637e 100644 --- a/src/app/system/codes/view-code/view-code.component.scss +++ b/src/app/system/codes/view-code/view-code.component.scss @@ -212,6 +212,7 @@ $gray-900: #202124; &--desc { flex: 0 0 26%; padding-right: 8px; } &--pos { flex: 0 0 14%; padding-right: 8px; } &--active { flex: 0 0 12%; padding-right: 8px; } + &--actions { flex: 0 0 26%; display: flex; @@ -272,7 +273,7 @@ $gray-900: #202124; } // Responsive -@media (max-width: 768px) { +@media (width <= 768px) { .vc-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/system/roles-and-permissions/add-role/add-role.component.scss b/src/app/system/roles-and-permissions/add-role/add-role.component.scss index 143f8221c..de51f75a0 100644 --- a/src/app/system/roles-and-permissions/add-role/add-role.component.scss +++ b/src/app/system/roles-and-permissions/add-role/add-role.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ar-field { diff --git a/src/app/system/roles-and-permissions/edit-role/edit-role.component.scss b/src/app/system/roles-and-permissions/edit-role/edit-role.component.scss index e2407e189..960ff2333 100644 --- a/src/app/system/roles-and-permissions/edit-role/edit-role.component.scss +++ b/src/app/system/roles-and-permissions/edit-role/edit-role.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .er-field { diff --git a/src/app/system/roles-and-permissions/view-role/view-role.component.scss b/src/app/system/roles-and-permissions/view-role/view-role.component.scss index f021468e2..d860f41e1 100644 --- a/src/app/system/roles-and-permissions/view-role/view-role.component.scss +++ b/src/app/system/roles-and-permissions/view-role/view-role.component.scss @@ -584,7 +584,7 @@ $gray-900: #202124; } // Responsive -@media (max-width: 768px) { +@media (width <= 768px) { .vr-topbar { flex-direction: column; align-items: flex-start; diff --git a/src/app/system/system.component.scss b/src/app/system/system.component.scss index ca9bfdf30..7408fded2 100644 --- a/src/app/system/system.component.scss +++ b/src/app/system/system.component.scss @@ -61,7 +61,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -82,7 +82,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; background: $gray-50; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + box-shadow: 0 2px 8px rgb(0 0 0 / 4%); .sys-card__arrow { color: $gray-700; @@ -105,17 +105,17 @@ $gray-900: #0f172a; font-size: 15px; flex-shrink: 0; - &--blue { background: rgba(59, 130, 246, 0.1); color: #2563eb; } - &--amber { background: rgba(245, 158, 11, 0.1); color: #d97706; } - &--green { background: rgba(34, 197, 94, 0.1); color: #16a34a; } - &--emerald { background: rgba(16, 185, 129, 0.1); color: #059669; } - &--purple { background: rgba(139, 92, 246, 0.1); color: #7c3aed; } - &--indigo { background: rgba(99, 102, 241, 0.1); color: #4f46e5; } - &--slate { background: rgba(100, 116, 139, 0.1); color: #475569; } - &--orange { background: rgba(249, 115, 22, 0.1); color: #ea580c; } - &--teal { background: rgba(20, 184, 166, 0.1); color: #0d9488; } - &--cyan { background: rgba(6, 182, 212, 0.1); color: #0891b2; } - &--rose { background: rgba(244, 63, 94, 0.1); color: #e11d48; } + &--blue { background: rgb(59 130 246 / 10%); color: #2563eb; } + &--amber { background: rgb(245 158 11 / 10%); color: #d97706; } + &--green { background: rgb(34 197 94 / 10%); color: #16a34a; } + &--emerald { background: rgb(16 185 129 / 10%); color: #059669; } + &--purple { background: rgb(139 92 246 / 10%); color: #7c3aed; } + &--indigo { background: rgb(99 102 241 / 10%); color: #4f46e5; } + &--slate { background: rgb(100 116 139 / 10%); color: #475569; } + &--orange { background: rgb(249 115 22 / 10%); color: #ea580c; } + &--teal { background: rgb(20 184 166 / 10%); color: #0d9488; } + &--cyan { background: rgb(6 182 212 / 10%); color: #0891b2; } + &--rose { background: rgb(244 63 94 / 10%); color: #e11d48; } } &__body { diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.scss index 03dce57e1..bcb3e38fa 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.scss @@ -84,7 +84,7 @@ $text-muted: #64748b; border-radius: 8px !important; height: 36px; padding: 0 16px; - font-family: 'DM Sans', 'Roboto', sans-serif; + font-family: 'DM Sans', Roboto, sans-serif; fa-icon { margin-right: 4px; @@ -169,7 +169,7 @@ $text-muted: #64748b; } // Responsive -@media (max-width: 768px) { +@media (width <= 768px) { .cv-action-bar { flex-direction: column; align-items: stretch; diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/client-approval/client-approval.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/client-approval/client-approval.component.scss index 73ba0c9bb..1626355c9 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/client-approval/client-approval.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/client-approval/client-approval.component.scss @@ -70,7 +70,7 @@ $text-muted: #64748b; border-radius: 8px; height: 32px; padding: 0 14px; - font-family: 'DM Sans', 'Roboto', sans-serif; + font-family: 'DM Sans', Roboto, sans-serif; fa-icon { margin-right: 4px; @@ -100,7 +100,7 @@ $text-muted: #64748b; margin-bottom: 12px; } -@media (max-width: 768px) { +@media (width <= 768px) { .cv-action-bar { flex-direction: column; align-items: stretch; diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/loan-approval/loan-approval.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/loan-approval/loan-approval.component.scss index 73ba0c9bb..1626355c9 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/loan-approval/loan-approval.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/loan-approval/loan-approval.component.scss @@ -70,7 +70,7 @@ $text-muted: #64748b; border-radius: 8px; height: 32px; padding: 0 14px; - font-family: 'DM Sans', 'Roboto', sans-serif; + font-family: 'DM Sans', Roboto, sans-serif; fa-icon { margin-right: 4px; @@ -100,7 +100,7 @@ $text-muted: #64748b; margin-bottom: 12px; } -@media (max-width: 768px) { +@media (width <= 768px) { .cv-action-bar { flex-direction: column; align-items: stretch; diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/loan-disbursal/loan-disbursal.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/loan-disbursal/loan-disbursal.component.scss index 31c372da8..5b0c37a8d 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/loan-disbursal/loan-disbursal.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/loan-disbursal/loan-disbursal.component.scss @@ -70,7 +70,7 @@ $text-muted: #64748b; border-radius: 8px; height: 32px; padding: 0 14px; - font-family: 'DM Sans', 'Roboto', sans-serif; + font-family: 'DM Sans', Roboto, sans-serif; fa-icon { margin-right: 4px; @@ -87,7 +87,7 @@ $text-muted: #64748b; } } -@media (max-width: 768px) { +@media (width <= 768px) { .cv-action-bar { flex-direction: column; align-items: stretch; diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.scss index 35ab4abac..1fc26e613 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.scss @@ -70,7 +70,7 @@ $text-muted: #64748b; border-radius: 8px; height: 32px; padding: 0 14px; - font-family: 'DM Sans', 'Roboto', sans-serif; + font-family: 'DM Sans', Roboto, sans-serif; fa-icon { margin-right: 4px; @@ -96,7 +96,7 @@ $text-muted: #64748b; } } -@media (max-width: 768px) { +@media (width <= 768px) { .cv-action-bar { flex-direction: column; align-items: stretch; diff --git a/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.scss b/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.scss index e6c6ebca8..14afdfa0e 100644 --- a/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.scss @@ -19,7 +19,7 @@ ::ng-deep { .mat-mdc-tab-link { - font-family: 'DM Sans', 'Roboto', sans-serif; + font-family: 'DM Sans', Roboto, sans-serif; font-size: 13px; font-weight: 500; letter-spacing: 0.02em; diff --git a/src/app/users/create-user/create-user.component.scss b/src/app/users/create-user/create-user.component.scss index f67b187c8..00bcd0378 100644 --- a/src/app/users/create-user/create-user.component.scss +++ b/src/app/users/create-user/create-user.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .cu-field { diff --git a/src/app/users/edit-user/edit-user.component.scss b/src/app/users/edit-user/edit-user.component.scss index 7ad59a8a7..cf07ad55f 100644 --- a/src/app/users/edit-user/edit-user.component.scss +++ b/src/app/users/edit-user/edit-user.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .eu-field { diff --git a/src/app/users/view-user/view-user.component.scss b/src/app/users/view-user/view-user.component.scss index 30920aae0..be626ba50 100644 --- a/src/app/users/view-user/view-user.component.scss +++ b/src/app/users/view-user/view-user.component.scss @@ -22,8 +22,8 @@ $accent: #0ea5e9; background: #fff; border-radius: 10px; overflow: hidden; - border: 1px solid rgba(0, 0, 0, 0.06); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 6%); + box-shadow: 0 1px 4px rgb(0 0 0 / 4%); } // ── Dark gradient header ── @@ -39,10 +39,7 @@ $accent: #0ea5e9; &::after { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background: repeating-linear-gradient(108deg, transparent 0, transparent 80px, rgb(255 255 255 / 2.5%) 80px, rgb(255 255 255 / 2.5%) 160px); pointer-events: none; } @@ -59,7 +56,7 @@ $accent: #0ea5e9; &__icon { font-size: 56px; - color: rgba(255, 255, 255, 0.25); + color: rgb(255 255 255 / 25%); } } @@ -92,23 +89,23 @@ $accent: #0ea5e9; &__username { font-size: 13px; font-weight: 500; - color: rgba(255, 255, 255, 0.5); - font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + color: rgb(255 255 255 / 50%); + font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace; } } .vu-menu-btn { - color: rgba(255, 255, 255, 0.7) !important; + color: rgb(255 255 255 / 70%) !important; width: 28px !important; height: 28px !important; min-width: 28px !important; padding: 0 !important; - border: 1px solid rgba(255, 255, 255, 0.15); + border: 1px solid rgb(255 255 255 / 15%); border-radius: 5px !important; line-height: 28px !important; &:hover { - background: rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 10%); color: #fff !important; } } @@ -125,14 +122,14 @@ $accent: #0ea5e9; align-items: center; gap: 8px; padding: 4px 11px; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.13); + background: rgb(255 255 255 / 10%); + border: 1px solid rgb(255 255 255 / 13%); border-radius: 5px; &__label { font-size: 9px; font-weight: 700; - color: rgba(255, 255, 255, 0.4); + color: rgb(255 255 255 / 40%); text-transform: uppercase; letter-spacing: 0.4px; } @@ -144,15 +141,15 @@ $accent: #0ea5e9; } &--active { - background: rgba(16, 185, 129, 0.15); - border-color: rgba(16, 185, 129, 0.25); + background: rgb(16 185 129 / 15%); + border-color: rgb(16 185 129 / 25%); .vu-chip__val { color: #6ee7b7; } } &--inactive { - background: rgba(239, 68, 68, 0.12); - border-color: rgba(239, 68, 68, 0.2); + background: rgb(239 68 68 / 12%); + border-color: rgb(239 68 68 / 20%); .vu-chip__val { color: #fca5a5; } } @@ -171,7 +168,7 @@ $accent: #0ea5e9; padding: 14px 16px; background: #f8fafc; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); margin: 3px; transition: background 0.12s; @@ -269,7 +266,7 @@ $accent: #0ea5e9; } // ── Responsive ── -@media (max-width: 768px) { +@media (width <= 768px) { .vu-top { flex-direction: column; align-items: center; @@ -281,7 +278,7 @@ $accent: #0ea5e9; .vu-grid { grid-template-columns: repeat(2, 1fr); } } -@media (max-width: 480px) { +@media (width <= 480px) { .vu-grid { grid-template-columns: 1fr; } } @@ -298,7 +295,7 @@ $accent: #0ea5e9; .vu-cell { background: #2a2a2a; - border-color: rgba(255, 255, 255, 0.06); + border-color: rgb(255 255 255 / 6%); &:hover { background: #333; } diff --git a/src/assets/images/cic-cibil.svg b/src/assets/images/cic-cibil.svg new file mode 100644 index 000000000..26e0e7851 --- /dev/null +++ b/src/assets/images/cic-cibil.svg @@ -0,0 +1,6 @@ + + + Trans + Union + CIBIL + diff --git a/src/assets/images/cic-crif.svg b/src/assets/images/cic-crif.svg new file mode 100644 index 000000000..9fad932f6 --- /dev/null +++ b/src/assets/images/cic-crif.svg @@ -0,0 +1,5 @@ + + + CRIF + HIGH MARK + diff --git a/src/assets/images/cic-equifax.svg b/src/assets/images/cic-equifax.svg new file mode 100644 index 000000000..4532308ac --- /dev/null +++ b/src/assets/images/cic-equifax.svg @@ -0,0 +1,5 @@ + + + EFX + + diff --git a/src/assets/images/cic-experian.svg b/src/assets/images/cic-experian.svg new file mode 100644 index 000000000..0be8f7373 --- /dev/null +++ b/src/assets/images/cic-experian.svg @@ -0,0 +1,5 @@ + + + EXP + + diff --git a/src/environments/.env.ts b/src/environments/.env.ts index 9dd2af68c..970461220 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -1,17 +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/. - */ - // IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! /* tslint:disable */ export default { 'mifos_x': { - 'version': '260407', - 'hash': 'gb890d24bb' + 'version': '260415', + 'hash': 'g79e455c4e' }, 'allow_switching_backend_instance': true };