@djb25/digit-ui-module-ekyc 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +1006 -489
- package/dist/index.modern.js.map +1 -1
- package/package.json +4 -2
- package/src/components/DesktopInbox.js +207 -59
- package/src/components/StatusCards.js +167 -16
- package/src/pages/employee/AadhaarVerification.js +323 -280
- package/src/pages/employee/AddressDetails.js +124 -100
- package/src/pages/employee/Create.js +31 -19
- package/src/pages/employee/Inbox.js +11 -4
- package/src/pages/employee/PropertyInfo.js +382 -319
- package/src/pages/employee/Review.js +314 -226
- package/src/utils/index.js +46 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djb25/digit-ui-module-ekyc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Digit UI Module for Ekyc",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.modern.js",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"react": "17.0.2",
|
|
24
24
|
"react-dom": "17.0.2",
|
|
25
25
|
"react-i18next": "11.16.2",
|
|
26
|
-
"react-
|
|
26
|
+
"react-icons": "^5.6.0",
|
|
27
|
+
"react-router-dom": "5.3.0",
|
|
28
|
+
"chart.js": "^4.4.1"
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
1
|
+
import React, { useMemo, useState } from "react";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
|
-
import { Table, SubmitBar, Header, Card, HomeIcon, PersonIcon } from "@djb25/digit-ui-react-components";
|
|
3
|
+
import { Table, SubmitBar, Header, Card, HomeIcon, PersonIcon, Modal, Loader } from "@djb25/digit-ui-react-components";
|
|
4
4
|
import { Link } from "react-router-dom";
|
|
5
5
|
import StatusCards from "./StatusCards";
|
|
6
6
|
|
|
@@ -23,19 +23,148 @@ const DesktopInbox = ({ tableConfig, filterComponent, ...props }) => {
|
|
|
23
23
|
searchFields,
|
|
24
24
|
} = props;
|
|
25
25
|
const { t } = useTranslation();
|
|
26
|
+
const tenantId = Digit.ULBService.getCurrentTenantId();
|
|
26
27
|
const [FilterComponent, setComp] = React.useState(() => Digit.ComponentRegistryService?.getComponent(filterComponent));
|
|
27
28
|
|
|
29
|
+
// State for Review Modal
|
|
30
|
+
const [showReviewModal, setShowReviewModal] = useState(false);
|
|
31
|
+
const [reviewHtml, setReviewHtml] = useState("");
|
|
32
|
+
const [selectedKno, setSelectedKno] = useState("");
|
|
33
|
+
|
|
34
|
+
const generateReviewHtml = (info) => {
|
|
35
|
+
if (!info) return "<h3>No data found</h3>";
|
|
36
|
+
|
|
37
|
+
// Helper to format labels
|
|
38
|
+
const formatLabel = (str) => str.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase());
|
|
39
|
+
|
|
40
|
+
return `
|
|
41
|
+
<!DOCTYPE html>
|
|
42
|
+
<html>
|
|
43
|
+
<head>
|
|
44
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
45
|
+
<style>
|
|
46
|
+
body { font-family: 'Inter', sans-serif; padding: 30px; color: #101828; line-height: 1.5; background: #fff; }
|
|
47
|
+
.header { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid #185FA5; padding-bottom: 20px; margin-bottom: 30px; }
|
|
48
|
+
.title { margin: 0; color: #185FA5; font-size: 24px; font-weight: 700; }
|
|
49
|
+
.subtitle { margin: 5px 0 0; color: #667085; font-size: 14px; }
|
|
50
|
+
.section { margin-bottom: 30px; border: 1px solid #EAECF0; border-radius: 12px; overflow: hidden; }
|
|
51
|
+
.section-header { background: #F9FAFB; padding: 12px 20px; border-bottom: 1px solid #EAECF0; font-weight: 700; font-size: 14px; color: #344054; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
52
|
+
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0; }
|
|
53
|
+
.item { padding: 16px 20px; border-bottom: 1px solid #F2F4F7; }
|
|
54
|
+
.item:nth-last-child(-n+2) { border-bottom: none; }
|
|
55
|
+
.label { font-size: 11px; color: #667085; text-transform: uppercase; font-weight: 600; letter-spacing: 0.02em; margin-bottom: 4px; }
|
|
56
|
+
.value { font-size: 14px; font-weight: 500; color: #1D2939; }
|
|
57
|
+
.badge { display: inline-block; padding: 4px 12px; border-radius: 16px; font-size: 12px; font-weight: 600; }
|
|
58
|
+
.badge-success { background: #ECFDF3; color: #027A48; }
|
|
59
|
+
.badge-warning { background: #FFFAEB; color: #B54708; }
|
|
60
|
+
.print-btn { background: #185FA5; color: #fff; padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; display: block; margin: 20px auto; }
|
|
61
|
+
@media print { .print-btn { display: none; } body { padding: 0; } }
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<div class="header">
|
|
66
|
+
<div>
|
|
67
|
+
<h1 class="title">Delhi Jal Board</h1>
|
|
68
|
+
<p class="subtitle">EKYC Application Review Summary</p>
|
|
69
|
+
</div>
|
|
70
|
+
<div style="text-align: right">
|
|
71
|
+
<span class="badge ${info.statusFlag === 'ACTIVE' ? 'badge-success' : 'badge-warning'}">${info.statusFlag || 'N/A'}</span>
|
|
72
|
+
<p class="subtitle" style="margin-top: 8px">Generated on: ${new Date().toLocaleDateString()}</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div class="section">
|
|
77
|
+
<div class="section-header">Basic Details</div>
|
|
78
|
+
<div class="grid">
|
|
79
|
+
<div class="item"><div class="label">KNO Number</div><div class="value">${info.kno || 'N/A'}</div></div>
|
|
80
|
+
<div class="item"><div class="label">Consumer Name</div><div class="value">${info.consumerName || 'N/A'}</div></div>
|
|
81
|
+
<div class="item"><div class="label">Mobile Number</div><div class="value">${info.mobileNo || 'N/A'}</div></div>
|
|
82
|
+
<div class="item"><div class="label">Email Address</div><div class="value">${info.email || 'N/A'}</div></div>
|
|
83
|
+
<div class="item"><div class="label">Connection Type</div><div class="value">${info.typeOfConnection || 'N/A'}</div></div>
|
|
84
|
+
<div class="item"><div class="label">Category</div><div class="value">${info.connectionCategory || 'N/A'}</div></div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="section">
|
|
89
|
+
<div class="section-header">Location & Property Information</div>
|
|
90
|
+
<div class="grid">
|
|
91
|
+
<div class="item"><div class="label">Address</div><div class="value">${info.addressRaw || 'N/A'}</div></div>
|
|
92
|
+
<div class="item"><div class="label">Locality</div><div class="value">${info.locality || 'N/A'}</div></div>
|
|
93
|
+
<div class="item"><div class="label">City - Pincode</div><div class="value">${info.city || 'N/A'} - ${info.pincode || 'N/A'}</div></div>
|
|
94
|
+
<div class="item"><div class="label">PID Number</div><div class="value">${info.pidNumber || 'N/A'}</div></div>
|
|
95
|
+
<div class="item"><div class="label">No. of Floors</div><div class="value">${info.noOfFloor || 'N/A'}</div></div>
|
|
96
|
+
<div class="item"><div class="label">Verification Status</div><div class="value"><span class="badge ${info.verificationStatus === 'SUCCESSFUL' ? 'badge-success' : 'badge-warning'}">${info.verificationStatus || 'PENDING'}</span></div></div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="section">
|
|
101
|
+
<div class="section-header">Meter Information</div>
|
|
102
|
+
<div class="grid">
|
|
103
|
+
<div class="item"><div class="label">Meter Number</div><div class="value">${info.meterNumber || 'N/A'}</div></div>
|
|
104
|
+
<div class="item"><div class="label">Meter Make</div><div class="value">${info.meterMake || 'N/A'}</div></div>
|
|
105
|
+
<div class="item"><div class="label">Meter Location</div><div class="value">${info.meterLocationAddress || 'N/A'}</div></div>
|
|
106
|
+
<div class="item"><div class="label">Working Status</div><div class="value">${info.workingStatus ? 'Working' : 'Not Working'}</div></div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<button class="print-btn" onclick="window.print()">Print This Review</button>
|
|
111
|
+
</body>
|
|
112
|
+
</html>
|
|
113
|
+
`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Use the library hook if available, otherwise fallback to local definition
|
|
117
|
+
// This is a safety measure to handle stale library builds in dev environment
|
|
118
|
+
const useReviewHook = Digit.Hooks.ekyc?.useEkycApplicationReview || ((p, config) => {
|
|
119
|
+
return Digit.Hooks.useMutation((data) => Digit.EkycService.application_review(data, p), config);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const { mutate: getReview, isLoading: isReviewLoading } = useReviewHook(
|
|
123
|
+
{ tenantId },
|
|
124
|
+
{
|
|
125
|
+
onSuccess: (res) => {
|
|
126
|
+
if (res?.applicationReviewInfo) {
|
|
127
|
+
const html = generateReviewHtml(res.applicationReviewInfo);
|
|
128
|
+
setReviewHtml(html);
|
|
129
|
+
setShowReviewModal(true);
|
|
130
|
+
} else {
|
|
131
|
+
// Fallback to URL method if the API is updated later to return a URL
|
|
132
|
+
const url = res?.acknowledgementURL || res?.reviewUrl || res?.url;
|
|
133
|
+
if (url) {
|
|
134
|
+
setReviewHtml(""); // Clear HTML so iframe uses URL
|
|
135
|
+
setReviewUrl(url);
|
|
136
|
+
setShowReviewModal(true);
|
|
137
|
+
} else {
|
|
138
|
+
alert(t("EKYC_REVIEW_INFO_NOT_FOUND"));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
onError: (err) => {
|
|
143
|
+
alert(err?.message || t("ERR_FAILED_TO_FETCH_REVIEW"));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const handleReview = (kno) => {
|
|
149
|
+
setSelectedKno(kno);
|
|
150
|
+
getReview({ kno });
|
|
151
|
+
};
|
|
152
|
+
|
|
28
153
|
const columns = useMemo(
|
|
29
154
|
() => [
|
|
30
155
|
{
|
|
31
156
|
Header: t("EKYC_APPLICATION_NO"),
|
|
32
157
|
accessor: "applicationNumber",
|
|
33
158
|
Cell: ({ row }) => {
|
|
34
|
-
const
|
|
159
|
+
const kno = row.original?.kno || row.original?.applicationNumber || "NA";
|
|
35
160
|
return (
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
161
|
+
<span
|
|
162
|
+
className="ekyc-application-link"
|
|
163
|
+
style={{ color: "#add8f7", cursor: "pointer", fontWeight: "bold" }}
|
|
164
|
+
onClick={() => handleReview(kno)}
|
|
165
|
+
>
|
|
166
|
+
{kno}
|
|
167
|
+
</span>
|
|
39
168
|
);
|
|
40
169
|
},
|
|
41
170
|
},
|
|
@@ -66,69 +195,88 @@ const DesktopInbox = ({ tableConfig, filterComponent, ...props }) => {
|
|
|
66
195
|
}, [data]);
|
|
67
196
|
|
|
68
197
|
return (
|
|
69
|
-
<div className="
|
|
70
|
-
<div className="
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<FilterComponent
|
|
85
|
-
defaultSearchParams={props.defaultSearchParams}
|
|
86
|
-
onFilterChange={props.onSearch}
|
|
87
|
-
searchParams={searchParams}
|
|
88
|
-
type="desktop"
|
|
89
|
-
moduleCode="EKYC"
|
|
198
|
+
<div className="ground-container employee-app-container form-container">
|
|
199
|
+
<div className="inbox-container" style={{ paddingBottom: "16px" }}>
|
|
200
|
+
{showReviewModal && (
|
|
201
|
+
<Modal
|
|
202
|
+
headerBarMain={t("EKYC_APPLICATION_REVIEW") + (selectedKno ? ` - ${selectedKno}` : "")}
|
|
203
|
+
headerBarEnd={<div style={{ cursor: "pointer", padding: "5px 10px", background: "#F2F4F7", borderRadius: "4px" }} onClick={() => setShowReviewModal(false)}>{t("CLOSE")}</div>}
|
|
204
|
+
hideSubmit={true}
|
|
205
|
+
popupStyles={{ width: "90%", height: "90%", maxWidth: "1000px" }}
|
|
206
|
+
popupModuleMianStyles={{ height: "calc(100% - 60px)", padding: 0 }}
|
|
207
|
+
>
|
|
208
|
+
<iframe
|
|
209
|
+
srcDoc={reviewHtml}
|
|
210
|
+
src={!reviewHtml ? reviewUrl : undefined}
|
|
211
|
+
title="Application Review"
|
|
212
|
+
style={{ width: "100%", height: "100%", border: "none" }}
|
|
90
213
|
/>
|
|
91
|
-
|
|
214
|
+
</Modal>
|
|
215
|
+
)}
|
|
216
|
+
{(isLoading || isReviewLoading) && <Loader />}
|
|
217
|
+
<div className="filters-container">
|
|
218
|
+
{/* Sidebar Title Card */}
|
|
219
|
+
<Card
|
|
220
|
+
className="sidebar-title-card"
|
|
221
|
+
style={{ display: "flex", alignItems: "center", padding: "16px", marginBottom: "16px", borderRadius: "4px" }}
|
|
222
|
+
>
|
|
223
|
+
<div className="icon-container" style={{ color: "#3A8DCC", marginRight: "12px" }}>
|
|
224
|
+
<HomeIcon style={{ width: "24px", height: "24px" }} />
|
|
225
|
+
</div>
|
|
226
|
+
<div style={{ fontWeight: "700", fontSize: "18px", color: "#0B0C0C" }}>{t("ACTION_TEST_EKYC")}</div>
|
|
227
|
+
</Card>
|
|
228
|
+
|
|
229
|
+
<div>
|
|
230
|
+
{FilterComponent && (
|
|
231
|
+
<FilterComponent
|
|
232
|
+
defaultSearchParams={props.defaultSearchParams}
|
|
233
|
+
onFilterChange={props.onSearch}
|
|
234
|
+
searchParams={searchParams}
|
|
235
|
+
type="desktop"
|
|
236
|
+
moduleCode="EKYC"
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
</div>
|
|
92
240
|
</div>
|
|
93
|
-
</div>
|
|
94
241
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
242
|
+
<div style={{ flex: 1, marginLeft: "16px" }}>
|
|
243
|
+
{/* Header Section (retaining for context/actions) */}
|
|
244
|
+
{/* <div className="ekyc-header-container module-header" style={{ marginBottom: "16px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
98
245
|
<Header className="title" style={{ margin: 0 }}>{t("EKYC_INBOX_HEADER")}</Header>
|
|
99
246
|
<Link to={`${parentRoute}/create-kyc`}>
|
|
100
247
|
<SubmitBar label={t("EKYC_CREATE_KYC")} style={{ borderRadius: "8px" }} />
|
|
101
248
|
</Link>
|
|
102
249
|
</div> */}
|
|
103
250
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</Card>
|
|
108
|
-
|
|
109
|
-
{/* Table Section */}
|
|
110
|
-
<div className="result" style={{ flex: 1 }}>
|
|
111
|
-
<Card className="ekyc-table-card" style={{ padding: 0 }}>
|
|
112
|
-
<Table
|
|
113
|
-
t={t}
|
|
114
|
-
data={tableData}
|
|
115
|
-
columns={columns}
|
|
116
|
-
isLoading={isLoading}
|
|
117
|
-
onSort={onSort}
|
|
118
|
-
sortParams={sortParams}
|
|
119
|
-
totalRecords={totalRecords}
|
|
120
|
-
onNextPage={onNextPage}
|
|
121
|
-
onPrevPage={onPrevPage}
|
|
122
|
-
currentPage={currentPage}
|
|
123
|
-
pageSizeLimit={pageSizeLimit}
|
|
124
|
-
onPageSizeChange={onPageSizeChange}
|
|
125
|
-
getCellProps={(cellInfo) => {
|
|
126
|
-
return {
|
|
127
|
-
className: "ekyc-table-cell",
|
|
128
|
-
};
|
|
129
|
-
}}
|
|
130
|
-
/>
|
|
251
|
+
{/* Metrics Section (The Card) */}
|
|
252
|
+
<Card className="ekyc-metrics-card" style={{ marginBottom: "16px", padding: "16px" }}>
|
|
253
|
+
<StatusCards countData={countData} />
|
|
131
254
|
</Card>
|
|
255
|
+
|
|
256
|
+
{/* Table Section */}
|
|
257
|
+
<div className="result" style={{ flex: 1 }}>
|
|
258
|
+
<Card className="ekyc-table-card" style={{ padding: 0 }}>
|
|
259
|
+
<Table
|
|
260
|
+
t={t}
|
|
261
|
+
data={tableData}
|
|
262
|
+
columns={columns}
|
|
263
|
+
isLoading={isLoading}
|
|
264
|
+
onSort={onSort}
|
|
265
|
+
sortParams={sortParams}
|
|
266
|
+
totalRecords={totalRecords}
|
|
267
|
+
onNextPage={onNextPage}
|
|
268
|
+
onPrevPage={onPrevPage}
|
|
269
|
+
currentPage={currentPage}
|
|
270
|
+
pageSizeLimit={pageSizeLimit}
|
|
271
|
+
onPageSizeChange={onPageSizeChange}
|
|
272
|
+
getCellProps={(cellInfo) => {
|
|
273
|
+
return {
|
|
274
|
+
className: "ekyc-table-cell",
|
|
275
|
+
};
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
</Card>
|
|
279
|
+
</div>
|
|
132
280
|
</div>
|
|
133
281
|
</div>
|
|
134
282
|
</div>
|
|
@@ -1,26 +1,177 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Card } from "@djb25/digit-ui-react-components";
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
3
2
|
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { Chart, registerables } from "chart.js";
|
|
4
|
+
|
|
5
|
+
Chart.register(...registerables);
|
|
4
6
|
|
|
5
7
|
const StatusCards = ({ countData }) => {
|
|
6
|
-
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
const chartRef1 = useRef(null);
|
|
10
|
+
const chartInstance1 = useRef(null);
|
|
11
|
+
const chartRef2 = useRef(null);
|
|
12
|
+
const chartInstance2 = useRef(null);
|
|
13
|
+
|
|
14
|
+
const total = countData?.total || 0;
|
|
15
|
+
const pending = countData?.pending || 0;
|
|
16
|
+
const active = countData?.completed || 0; // Showing completed data as active
|
|
17
|
+
const completed = 0; // Forced to 0
|
|
18
|
+
const rejected = countData?.rejected || 0;
|
|
19
|
+
|
|
20
|
+
const actualCompleted = countData?.completed || 0;
|
|
21
|
+
const applied = total;
|
|
22
|
+
const approved = actualCompleted;
|
|
23
|
+
|
|
24
|
+
const efficiency = total > 0 ? Math.round((actualCompleted / total) * 100) : 0;
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
// Chart 1: Status Breakdown
|
|
28
|
+
if (chartRef1.current) {
|
|
29
|
+
if (chartInstance1.current) chartInstance1.current.destroy();
|
|
30
|
+
const ctx1 = chartRef1.current.getContext("2d");
|
|
31
|
+
chartInstance1.current = new Chart(ctx1, {
|
|
32
|
+
type: "doughnut",
|
|
33
|
+
data: {
|
|
34
|
+
labels: [t("EKYC_ACTIVE"), t("EKYC_COMPLETED"), t("EKYC_PENDING")],
|
|
35
|
+
datasets: [{
|
|
36
|
+
data: [active, completed, pending],
|
|
37
|
+
backgroundColor: ["#1a3a6b", "#77B6EA", "#3d84ed"],
|
|
38
|
+
borderColor: ["#ffffff", "#ffffff", "#ffffff"],
|
|
39
|
+
borderWidth: 2,
|
|
40
|
+
hoverOffset: 4,
|
|
41
|
+
}],
|
|
42
|
+
},
|
|
43
|
+
options: {
|
|
44
|
+
cutout: "75%",
|
|
45
|
+
plugins: { legend: { display: false } },
|
|
46
|
+
maintainAspectRatio: true,
|
|
47
|
+
responsive: true,
|
|
48
|
+
aspectRatio: 1,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Chart 2: Applied vs Approved
|
|
54
|
+
if (chartRef2.current) {
|
|
55
|
+
if (chartInstance2.current) chartInstance2.current.destroy();
|
|
56
|
+
const ctx2 = chartRef2.current.getContext("2d");
|
|
57
|
+
chartInstance2.current = new Chart(ctx2, {
|
|
58
|
+
type: "doughnut",
|
|
59
|
+
data: {
|
|
60
|
+
labels: [t("EKYC_APPROVED"), t("EKYC_OTHERS")],
|
|
61
|
+
datasets: [{
|
|
62
|
+
data: [approved, Math.max(0, applied - approved)],
|
|
63
|
+
backgroundColor: ["#219653", "#E0E0E0"],
|
|
64
|
+
borderColor: ["#ffffff", "#ffffff"],
|
|
65
|
+
borderWidth: 2,
|
|
66
|
+
hoverOffset: 4,
|
|
67
|
+
}],
|
|
68
|
+
},
|
|
69
|
+
options: {
|
|
70
|
+
cutout: "75%",
|
|
71
|
+
plugins: { legend: { display: false } },
|
|
72
|
+
maintainAspectRatio: true,
|
|
73
|
+
responsive: true,
|
|
74
|
+
aspectRatio: 1,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
7
78
|
|
|
8
|
-
return (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
79
|
+
return () => {
|
|
80
|
+
if (chartInstance1.current) chartInstance1.current.destroy();
|
|
81
|
+
if (chartInstance2.current) chartInstance2.current.destroy();
|
|
82
|
+
};
|
|
83
|
+
}, [pending, completed, active, applied, approved, t]);
|
|
84
|
+
|
|
85
|
+
const formatNumber = (num) => {
|
|
86
|
+
return new Intl.NumberFormat("en-IN").format(num || 0);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div style={{ fontFamily: "'Segoe UI', sans-serif", width: "100%" }}>
|
|
91
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", flexWrap: "wrap", gap: "24px" }}>
|
|
92
|
+
|
|
93
|
+
{/* Statistics Section */}
|
|
94
|
+
<div style={{ flex: "1", minWidth: "250px" }}>
|
|
95
|
+
<div style={{ fontSize: "11px", fontWeight: "700", color: "#888", letterSpacing: "1px", marginBottom: "16px", textTransform: "uppercase" }}>
|
|
96
|
+
{t("EKYC_DASHBOARD_METRICS") || "DASHBOARD METRICS"}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
|
100
|
+
{/* Active */}
|
|
101
|
+
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
102
|
+
<span style={{ width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "#1a3a6b", flexShrink: 0 }} />
|
|
103
|
+
<span style={{ fontSize: "14px", color: "#333" }}>
|
|
104
|
+
{t("EKYC_ACTIVE")}: <strong style={{ color: "#1a3a6b" }}>{formatNumber(active)}</strong>
|
|
105
|
+
</span>
|
|
13
106
|
</div>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
107
|
+
|
|
108
|
+
{/* Pending */}
|
|
109
|
+
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
110
|
+
<span style={{ width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "#3d84ed", flexShrink: 0 }} />
|
|
111
|
+
<span style={{ fontSize: "14px", color: "#333" }}>
|
|
112
|
+
{t("EKYC_PENDING")}: <strong style={{ color: "#1a3a6b" }}>{formatNumber(pending)}</strong>
|
|
113
|
+
</span>
|
|
17
114
|
</div>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
115
|
+
|
|
116
|
+
{/* Completed */}
|
|
117
|
+
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
118
|
+
<span style={{ width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "#77B6EA", flexShrink: 0 }} />
|
|
119
|
+
<span style={{ fontSize: "14px", color: "#333" }}>
|
|
120
|
+
{t("EKYC_COMPLETED")}: <strong style={{ color: "#1a3a6b" }}>{formatNumber(completed)}</strong>
|
|
121
|
+
</span>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{/* Total Section */}
|
|
125
|
+
<div style={{ marginTop: "20px", padding: "16px", background: "#f8faff", borderRadius: "8px", border: "1px solid #eef2f6" }}>
|
|
126
|
+
<div style={{ fontSize: "32px", fontWeight: "800", color: "#1a3a6b", lineHeight: 1 }}>
|
|
127
|
+
{formatNumber(total)}
|
|
128
|
+
</div>
|
|
129
|
+
<div style={{ fontSize: "12px", color: "#667085", marginTop: "4px", fontWeight: "600" }}>
|
|
130
|
+
{t("EKYC_TOTAL_APPLICATIONS") || "Total Applications Applied"}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Visualizations Section */}
|
|
137
|
+
<div style={{ display: "flex", gap: "32px", alignItems: "center", justifyContent: "flex-end", flexWrap: "wrap" }}>
|
|
138
|
+
|
|
139
|
+
{/* Chart 1: Status Breakdown */}
|
|
140
|
+
<div style={{ textAlign: "center" }}>
|
|
141
|
+
<div style={{ width: "140px", height: "140px", position: "relative" }}>
|
|
142
|
+
<canvas ref={chartRef1} />
|
|
143
|
+
<div style={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", textAlign: "center", pointerEvents: "none", width: "100%" }}>
|
|
144
|
+
{/* <div style={{ fontSize: "16px", fontWeight: "800", color: "#1a3a6b", lineHeight: 1.2 }}>
|
|
145
|
+
{formatNumber(active)} / {formatNumber(total)}
|
|
146
|
+
</div> */}
|
|
147
|
+
<div style={{ fontSize: "9px", color: "#667085", fontWeight: "700", marginTop: "4px" }}>
|
|
148
|
+
{efficiency}%
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
21
151
|
</div>
|
|
152
|
+
<div style={{ marginTop: "8px", fontSize: "11px", fontWeight: "700", color: "#888", textTransform: "uppercase" }}>{t("EKYC_STATUS_BREAKDOWN")}</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Chart 2: Applied vs Approved */}
|
|
156
|
+
<div style={{ textAlign: "center" }}>
|
|
157
|
+
<div style={{ width: "140px", height: "140px", position: "relative" }}>
|
|
158
|
+
<canvas ref={chartRef2} />
|
|
159
|
+
<div style={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", textAlign: "center", pointerEvents: "none", width: "100%" }}>
|
|
160
|
+
{/* <div style={{ fontSize: "16px", fontWeight: "800", color: "#219653", lineHeight: 1.2 }}>
|
|
161
|
+
{formatNumber(approved)} / {formatNumber(applied)}
|
|
162
|
+
</div> */}
|
|
163
|
+
<div style={{ fontSize: "9px", color: "#667085", fontWeight: "700", marginTop: "4px" }}>
|
|
164
|
+
{total > 0 ? Math.round((approved / total) * 100) : 0}%
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div style={{ marginTop: "8px", fontSize: "11px", fontWeight: "700", color: "#888", textTransform: "uppercase" }}>{t("EKYC_SUBMISSION_HEALTH")}</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
22
171
|
</div>
|
|
23
|
-
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
24
175
|
};
|
|
25
176
|
|
|
26
|
-
export default StatusCards;
|
|
177
|
+
export default StatusCards;
|