@djb25/digit-ui-module-ekyc 1.0.10 → 1.0.12

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.
Files changed (40) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.modern.js +1338 -591
  4. package/dist/index.modern.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/Module.js +25 -7
  7. package/src/components/AadhaarVerification.js +415 -0
  8. package/src/components/AddressDetails.js +207 -0
  9. package/src/components/CeoDashboard.js +205 -0
  10. package/src/components/DesktopInbox.js +1 -1
  11. package/src/components/EKYCCard.js +4 -0
  12. package/src/components/MeterDetails.js +372 -0
  13. package/src/components/PropertyInfo.js +303 -0
  14. package/src/components/Review.js +572 -0
  15. package/src/components/SearchFormFieldsComponent.js +3 -3
  16. package/src/components/analytics/charts/ClusterHeatmap.js +88 -0
  17. package/src/components/analytics/charts/TaskStatusChart.js +92 -0
  18. package/src/components/analytics/components/AnalyticsTable.js +106 -0
  19. package/src/components/analytics/components/DashboardLayout.js +72 -0
  20. package/src/components/analytics/components/EmptyState.js +27 -0
  21. package/src/components/analytics/components/ErrorBoundary.js +27 -0
  22. package/src/components/analytics/components/FilterBar.js +73 -0
  23. package/src/components/analytics/components/NotificationPanel.js +77 -0
  24. package/src/components/analytics/components/SLAWidget.js +56 -0
  25. package/src/components/analytics/components/SkeletonLoader.js +53 -0
  26. package/src/components/analytics/components/SummaryCard.js +74 -0
  27. package/src/components/analytics/components/WorkflowTimeline.js +55 -0
  28. package/src/components/analytics/styles/Dashboard.css +54 -0
  29. package/src/components/analytics/utils/exportUtils.js +64 -0
  30. package/src/components/analytics/utils/filterSerializer.js +50 -0
  31. package/src/config/config.js +1 -1
  32. package/src/pages/citizen/index.js +74 -18
  33. package/src/pages/employee/ConsumerDetails.js +10 -281
  34. package/src/pages/employee/Inbox.js +6 -4
  35. package/src/pages/employee/index.js +44 -8
  36. package/src/pages/employee/AadhaarVerification.js +0 -512
  37. package/src/pages/employee/AddressDetails.js +0 -548
  38. package/src/pages/employee/MeterDetails.js +0 -496
  39. package/src/pages/employee/PropertyInfo.js +0 -489
  40. package/src/pages/employee/Review.js +0 -314
@@ -0,0 +1,572 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ Card,
4
+ CardHeader,
5
+ CardSubHeader,
6
+ StatusTable,
7
+ Row,
8
+ SubmitBar,
9
+ Loader,
10
+ ActionBar,
11
+ CheckBox,
12
+ LinkButton,
13
+ EditIcon,
14
+ GenericFileIcon,
15
+ } from "@djb25/digit-ui-react-components";
16
+ import { useTranslation } from "react-i18next";
17
+ import { useHistory, useLocation } from "react-router-dom";
18
+
19
+
20
+ // ─── Constants ───────────────────────────────────────────────────────────────
21
+
22
+ const ActionButton = ({ jumpTo, state }) => {
23
+ const history = useHistory();
24
+ function routeTo() {
25
+ history.push(jumpTo, { ...state, isEditing: true });
26
+ }
27
+ return (
28
+ <LinkButton
29
+ label={<EditIcon style={{ width: "20px", height: "20px", fill: "#F47738" }} />}
30
+ style={{ margin: 0, padding: 0 }}
31
+ onClick={routeTo}
32
+ />
33
+ );
34
+ };
35
+
36
+ const checkForNA = (value) => (value !== null && value !== undefined && value !== "" ? value : "N/A");
37
+
38
+ const boolToYesNo = (value, t) => {
39
+ if (value === true || value === "true" || String(value).toLowerCase() === "yes") return t("CORE_COMMON_YES");
40
+ if (value === false || value === "false" || String(value).toLowerCase() === "no") return t("CORE_COMMON_NO");
41
+ if (value === "true") return t("CORE_COMMON_YES");
42
+ if (value === "false") return t("CORE_COMMON_NO");
43
+ return "N/A";
44
+ };
45
+
46
+ /**
47
+ * Robust data extraction for comparison.
48
+ * The API returns { applicationReviewInfo: { newData: { ... }, oldData: { ... } } }
49
+ */
50
+ const extractReviewData = (searchData, flowState) => {
51
+ const rawData = searchData && Object.keys(searchData).length > 0 ? searchData : flowState?.reviewData || {};
52
+
53
+ // Navigate through applicationReviewInfo -> newData/oldData
54
+ const reviewWrapper = rawData?.applicationReviewInfo || rawData?.applicationReview || rawData;
55
+ const applicationData = (Array.isArray(reviewWrapper) ? reviewWrapper[0] : reviewWrapper) || {};
56
+
57
+ return {
58
+ newData: applicationData?.newData || applicationData,
59
+ oldData: applicationData?.oldData || null
60
+ };
61
+ };
62
+
63
+ const ReviewSection = ({ title, fields, newData, oldData, t, jumpTo, state }) => {
64
+ return (
65
+ <div className="review-section-wrapper" style={{ marginBottom: "48px", background: "#fff", borderRadius: "12px", border: "1px solid #EAECF0", overflow: "hidden" }}>
66
+ <div style={{ padding: "20px 24px", background: "#F9FAFB", borderBottom: "1px solid #EAECF0", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
67
+ <CardSubHeader style={{ margin: 0, fontSize: "18px", color: "#101828", fontWeight: "700" }}>{title}</CardSubHeader>
68
+ {jumpTo && <ActionButton jumpTo={jumpTo} state={state} />}
69
+ </div>
70
+
71
+ <div style={{ padding: "0 24px" }}>
72
+ <table style={{ width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }}>
73
+ <thead>
74
+ <tr>
75
+ <th style={{ padding: "16px 0", textAlign: "left", color: "#667085", fontSize: "12px", fontWeight: "600", textTransform: "uppercase", width: "30%" }}>{t("EKYC_FIELD_NAME")}</th>
76
+ <th style={{ padding: "16px 0", textAlign: "left", color: "#667085", fontSize: "12px", fontWeight: "600", textTransform: "uppercase", width: "35%" }}>{t("EKYC_EXISTING_INFORMATION")}</th>
77
+ <th style={{ padding: "16px 0", textAlign: "left", color: "#667085", fontSize: "12px", fontWeight: "600", textTransform: "uppercase", width: "35%" }}>{t("EKYC_PROPOSED_UPDATES")}</th>
78
+ </tr>
79
+ </thead>
80
+ <tbody>
81
+ {fields.map((field, idx) => {
82
+ const valNew = newData?.[field.key];
83
+ const valOld = oldData?.[field.key];
84
+ const isChanged = oldData && String(valNew) !== String(valOld) && valOld !== undefined && valOld !== null;
85
+
86
+ return (
87
+ <tr key={idx} style={{ borderTop: "1px solid #F2F4F7" }}>
88
+ <td style={{ padding: "16px 0", fontSize: "14px", color: "#344054", fontWeight: "500" }}>
89
+ {t(field.label)}
90
+ </td>
91
+ <td style={{ padding: "16px 0", fontSize: "14px", color: "#667085" }}>
92
+ {field.isBool ? boolToYesNo(valOld, t) : checkForNA(valOld)}
93
+ </td>
94
+ <td style={{ padding: "16px 0", fontSize: "14px", color: isChanged ? "#1B8B32" : "#101828", fontWeight: isChanged ? "700" : "400" }}>
95
+ <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
96
+ {field.isBool ? boolToYesNo(valNew, t) : checkForNA(valNew)}
97
+ {isChanged && (
98
+ <span style={{ background: "#ECFDF3", color: "#067647", padding: "2px 8px", borderRadius: "12px", fontSize: "10px", fontWeight: "600" }}>
99
+ {t("EKYC_CHANGED")}
100
+ </span>
101
+ )}
102
+ </div>
103
+ </td>
104
+ </tr>
105
+ );
106
+ })}
107
+ </tbody>
108
+ </table>
109
+ </div>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ const Review = () => {
115
+
116
+ const { t } = useTranslation();
117
+ const history = useHistory();
118
+ const location = useLocation();
119
+
120
+ const [agree, setAgree] = useState(false);
121
+ const [isSubmitting, setIsSubmitting] = useState(false);
122
+ const [showPreview, setShowPreview] = useState(false);
123
+ const [previewUrl, setPreviewUrl] = useState("");
124
+
125
+ const flowState = location.state || {};
126
+ const { kNumber, kno, edits = {} } = flowState;
127
+ const activeKno = kNumber || kno;
128
+
129
+ const { aadhaarData = {}, addressDetails: editedAddress = {}, propertyDetails: editedProperty = {}, meterDetails: editedMeter = {} } = edits;
130
+
131
+ const tenantId = Digit.ULBService.getCurrentTenantId();
132
+ const workflowMutation = Digit.Hooks.ekyc.useEkycWorkflow(tenantId);
133
+ const updateMutation = Digit.Hooks.ekyc.useEkycUpdate(tenantId);
134
+
135
+ const { data: searchData, isLoading: isSearchLoading } = Digit.Hooks.ekyc.useEkycSearchReview({ kno: activeKno, fetchType: "REVIEW" }, tenantId, {
136
+ enabled: !!activeKno,
137
+ });
138
+
139
+ // ── Data Consolidation ──────────────────────────────────────────────────
140
+ const { newData: apiNewData, oldData: apiOldData } = extractReviewData(searchData, flowState);
141
+
142
+ const prepareConsolidatedData = (data) => {
143
+ if (!data) return null;
144
+ const apiConn = data?.connectionDetails || data || {};
145
+ const apiAddr = data?.addressDetails || data || {};
146
+ const apiProp = data?.propertyInfo || data || {};
147
+ const apiMeter = data?.meterDetails || data || {};
148
+
149
+ return {
150
+ connection: {
151
+ consumerName: apiConn?.consumerName || (apiConn?.firstName ? [apiConn.firstName, apiConn.middleName, apiConn.lastName].filter(Boolean).join(" ") : null),
152
+ address: apiConn?.address || apiConn?.addressRaw,
153
+ connectionType: apiConn?.connectionType || apiConn?.connectionCategory,
154
+ meterNumber: apiConn?.meterNumber || apiConn?.meterNo,
155
+ phoneNumber: apiConn?.phoneNumber || apiConn?.mobileNo || apiConn?.mobileNumber,
156
+ email: apiConn?.email,
157
+ statusflag: apiConn?.statusflag || apiConn?.statusFlag,
158
+ ekycStatus: apiConn?.ekycStatus,
159
+ knumber: apiConn?.knumber || apiConn?.kno,
160
+ },
161
+ address: {
162
+ fullAddress: apiAddr?.fullAddress || apiAddr?.addressRaw,
163
+ flatHouseNumber: apiAddr?.flatHouseNumber || apiAddr?.flatNo,
164
+ buildingTower: apiAddr?.buildingTower || apiAddr?.building,
165
+ landmark: apiAddr?.landmark,
166
+ pinCode: apiAddr?.pinCode || apiAddr?.pincode,
167
+ ward: apiAddr?.ward || apiAddr?.locality,
168
+ assembly: apiAddr?.assembly,
169
+ gpsValid: apiAddr?.gpsValid,
170
+ latitude: apiAddr?.latitude,
171
+ longitude: apiAddr?.longitude,
172
+ mobileNo: apiAddr?.mobileNo || apiAddr?.mobileNumber,
173
+ whatsappNo: apiAddr?.whatsappNo,
174
+ email: apiAddr?.email,
175
+ noOfPerson: apiAddr?.noOfPerson || apiAddr?.noOfPersons,
176
+ knumber: apiAddr?.knumber || apiAddr?.kno,
177
+ doorPhotoFilestoreId: apiAddr?.doorPhotoFilestoreId,
178
+ },
179
+ property: {
180
+ kno: apiProp?.kno,
181
+ pidNumber: apiProp?.pidNumber,
182
+ typeOfConnection: apiProp?.typeOfConnection,
183
+ connectionCategory: apiProp?.connectionCategory,
184
+ userType: apiProp?.userType,
185
+ numberOfFloors: apiProp?.numberOfFloors || apiProp?.noOfFloor,
186
+ tenantName: apiProp?.tenantName,
187
+ tenantMobile: apiProp?.tenantMobile,
188
+ ekycStatus: apiProp?.ekycStatus,
189
+ propertyDocumentFileStoreId: apiProp?.propertyDocumentFileStoreId,
190
+ buildingImageFileStoreId: apiProp?.buildingImageFileStoreId,
191
+ },
192
+ meter: {
193
+ kno: apiMeter?.kno,
194
+ metered: apiMeter?.meterStatus === "METERED" || apiMeter?.metered,
195
+ meterNumber: apiMeter?.meterNumber || apiMeter?.meterNo,
196
+ meterMake: apiMeter?.meterMake,
197
+ meterLocationAddress: apiMeter?.meterLocationAddress,
198
+ meterLatitude: apiMeter?.meterLatitude,
199
+ meterLongitude: apiMeter?.meterLongitude,
200
+ workingStatus: apiMeter?.workingStatus,
201
+ lastBillRaised: apiMeter?.lastBillRaised,
202
+ systemMeterId: apiMeter?.systemMeterId,
203
+ meterPhotoFileStoreId: apiMeter?.meterPhotoFileStoreId,
204
+ }
205
+ };
206
+ };
207
+
208
+ const newDataRaw = prepareConsolidatedData(apiNewData);
209
+ const oldDataRaw = prepareConsolidatedData(apiOldData);
210
+
211
+ // Apply edits to newData if present
212
+ const connectionData = {
213
+ ...newDataRaw?.connection,
214
+ consumerName: aadhaarData?.name || newDataRaw?.connection?.consumerName,
215
+ phoneNumber: aadhaarData?.mobileNumber || newDataRaw?.connection?.phoneNumber,
216
+ knumber: newDataRaw?.connection?.knumber || activeKno,
217
+ };
218
+
219
+ const addressData = {
220
+ ...newDataRaw?.address,
221
+ fullAddress: editedAddress?.fullAddress || newDataRaw?.address?.fullAddress,
222
+ flatHouseNumber: editedAddress?.flatHouseNumber || editedAddress?.flatNo || newDataRaw?.address?.flatHouseNumber,
223
+ buildingTower: editedAddress?.buildingTower || editedAddress?.building || newDataRaw?.address?.buildingTower,
224
+ landmark: editedAddress?.landmark || newDataRaw?.address?.landmark,
225
+ pinCode: editedAddress?.pinCode || editedAddress?.pincode || newDataRaw?.address?.pinCode,
226
+ ward: editedAddress?.ward || newDataRaw?.address?.ward,
227
+ assembly: editedAddress?.assembly || newDataRaw?.address?.assembly,
228
+ gpsValid: editedAddress?.gpsValid !== undefined ? editedAddress.gpsValid : newDataRaw?.address?.gpsValid,
229
+ latitude: editedAddress?.latitude || newDataRaw?.address?.latitude,
230
+ longitude: editedAddress?.longitude || newDataRaw?.address?.longitude,
231
+ mobileNo: editedAddress?.mobileNo || aadhaarData?.mobileNumber || newDataRaw?.address?.mobileNo,
232
+ whatsappNo: editedAddress?.whatsappNo || aadhaarData?.whatsappNumber || newDataRaw?.address?.whatsappNo,
233
+ email: editedAddress?.email || newDataRaw?.address?.email,
234
+ noOfPerson: editedAddress?.noOfPerson || aadhaarData?.noOfPersons || newDataRaw?.address?.noOfPerson,
235
+ knumber: editedAddress?.knumber || newDataRaw?.address?.knumber || activeKno,
236
+ doorPhotoFilestoreId: editedAddress?.doorPhotoFileStoreId || newDataRaw?.address?.doorPhotoFilestoreId,
237
+ };
238
+
239
+ const propertyData = {
240
+ ...newDataRaw?.property,
241
+ kno: newDataRaw?.property?.kno || activeKno,
242
+ pidNumber: editedProperty?.pidNumber || newDataRaw?.property?.pidNumber,
243
+ typeOfConnection: editedProperty?.connectionTypeData?.label || newDataRaw?.property?.typeOfConnection,
244
+ connectionCategory: editedProperty?.connectionCategoryData?.label || newDataRaw?.property?.connectionCategory,
245
+ userType: editedProperty?.userTypeData?.label || newDataRaw?.property?.userType,
246
+ numberOfFloors: editedProperty?.noOfFloorsData?.label || newDataRaw?.property?.numberOfFloors,
247
+ propertyDocumentFileStoreId: editedProperty?.propertyDocumentFileStoreId || newDataRaw?.property?.propertyDocumentFileStoreId,
248
+ };
249
+
250
+ const meterData = {
251
+ ...newDataRaw?.meter,
252
+ kno: editedMeter?.kno || newDataRaw?.meter?.kno || activeKno,
253
+ metered: editedMeter?.meterStatusData?.value === "Metered" || newDataRaw?.meter?.metered,
254
+ meterNumber: newDataRaw?.meter?.meterNumber,
255
+ meterMake: editedMeter?.meterMake || newDataRaw?.meter?.meterMake,
256
+ meterLocationAddress: editedMeter?.meterLocation || newDataRaw?.meter?.meterLocationAddress,
257
+ workingStatus: editedMeter?.workingStatusData?.value === "Working" || newDataRaw?.meter?.workingStatus,
258
+ lastBillRaised: editedMeter?.lastBillRaisedData?.value === "Yes" || newDataRaw?.meter?.lastBillRaised,
259
+ meterPhotoFileStoreId: editedMeter?.meterPhotoFileStoreId || newDataRaw?.meter?.meterPhotoFileStoreId,
260
+ };
261
+
262
+ // ── Section Fields Configuration ─────────────────────────────────────
263
+ const connectionFields = [
264
+ { label: "EKYC_K_NUMBER", key: "knumber" },
265
+ { label: "EKYC_CONSUMER_NAME", key: "consumerName" },
266
+ { label: "EKYC_ADDRESS", key: "address" },
267
+ { label: "EKYC_CONNECTION_TYPE", key: "connectionType" },
268
+ { label: "EKYC_METER_NO", key: "meterNumber" },
269
+ { label: "EKYC_MOBILE_NO", key: "phoneNumber" },
270
+ { label: "EKYC_EMAIL", key: "email" },
271
+ { label: "EKYC_STATUS_FLAG", key: "statusflag" },
272
+ { label: "EKYC_STATUS", key: "ekycStatus" },
273
+ ];
274
+
275
+ const addressFields = [
276
+ { label: "EKYC_FULL_ADDRESS", key: "fullAddress" },
277
+ { label: "EKYC_FLAT_HOUSE_NO", key: "flatHouseNumber" },
278
+ { label: "EKYC_BUILDING_TOWER", key: "buildingTower" },
279
+ { label: "EKYC_LANDMARK", key: "landmark" },
280
+ { label: "EKYC_PINCODE", key: "pinCode" },
281
+ { label: "EKYC_LOCALITY", key: "ward" },
282
+ { label: "EKYC_ASSEMBLY", key: "assembly" },
283
+ { label: "EKYC_GPS_VALID", key: "gpsValid", isBool: true },
284
+ { label: "EKYC_LATITUDE", key: "latitude" },
285
+ { label: "EKYC_LONGITUDE", key: "longitude" },
286
+ { label: "EKYC_MOBILE_NO", key: "mobileNo" },
287
+ { label: "EKYC_WHATSAPP_NO", key: "whatsappNo" },
288
+ { label: "EKYC_EMAIL", key: "email" },
289
+ { label: "EKYC_NO_OF_PERSONS", key: "noOfPerson" },
290
+ { label: "EKYC_K_NUMBER", key: "knumber" },
291
+ ];
292
+
293
+ const propertyFields = [
294
+ { label: "EKYC_CONNECTION_CATEGORY", key: "connectionCategory" },
295
+ { label: "EKYC_PID_NUMBER", key: "pidNumber" },
296
+ { label: "EKYC_TYPE_OF_CONNECTION", key: "typeOfConnection" },
297
+ { label: "EKYC_USER_TYPE", key: "userType" },
298
+ { label: "EKYC_FLOOR_COUNT", key: "numberOfFloors" },
299
+ { label: "EKYC_TENANT_NAME", key: "tenantName" },
300
+ { label: "EKYC_TENANT_MOBILE", key: "tenantMobile" },
301
+ { label: "EKYC_STATUS", key: "ekycStatus" },
302
+ ];
303
+
304
+ const meterFields = [
305
+ { label: "EKYC_METERED", key: "metered", isBool: true },
306
+ { label: "EKYC_METER_NO", key: "meterNumber" },
307
+ { label: "EKYC_METER_MAKE", key: "meterMake" },
308
+ { label: "EKYC_METER_LOCATION_ADDRESS", key: "meterLocationAddress" },
309
+ { label: "EKYC_METER_LATITUDE", key: "meterLatitude" },
310
+ { label: "EKYC_METER_LONGITUDE", key: "meterLongitude" },
311
+ { label: "EKYC_WORKING_STATUS", key: "workingStatus", isBool: true },
312
+ { label: "EKYC_LAST_BILL_RAISED", key: "lastBillRaised", isBool: true },
313
+ { label: "EKYC_SYSTEM_METER_ID", key: "systemMeterId" },
314
+ ];
315
+
316
+ const handleDeclaration = () => setAgree(!agree);
317
+
318
+ const handleReject = async () => {
319
+ const payload = {
320
+ RequestInfo: {
321
+ apiId: "Rainmaker",
322
+ ver: "1.0",
323
+ msgId: "message-id",
324
+ authToken: Digit.UserService.getUser()?.access_token,
325
+ },
326
+ kno: activeKno,
327
+ action: "REJECTED",
328
+ remarks: "Application rejected by reviewer",
329
+ reviewedBy: Digit.UserService.getUser()?.info?.userName,
330
+ role: "ZRO",
331
+ };
332
+ try {
333
+ const result = await workflowMutation.mutateAsync(payload);
334
+ if (result) {
335
+ history.push("/digit-ui/employee/ekyc/response", { success: true, result });
336
+ }
337
+ } catch (err) {
338
+ console.error("Reject Error:", err);
339
+ }
340
+ };
341
+
342
+ const handleApprove = async () => {
343
+ const payload = {
344
+ RequestInfo: {
345
+ apiId: "Rainmaker",
346
+ ver: "1.0",
347
+ msgId: "message-id",
348
+ authToken: Digit.UserService.getUser()?.access_token,
349
+ },
350
+ kno: activeKno,
351
+ action: "APPROVED",
352
+ remarks: "All documents verified",
353
+ reviewedBy: Digit.UserService.getUser()?.info?.userName,
354
+ role: "ZRO",
355
+ };
356
+ try {
357
+ const result = await workflowMutation.mutateAsync(payload);
358
+ if (result) {
359
+ history.push("/digit-ui/employee/ekyc/response", { success: true, result });
360
+ }
361
+ } catch (err) {
362
+ console.error("Approve Error:", err);
363
+ }
364
+ };
365
+
366
+ const handleFinalSubmit = async () => {
367
+ setIsSubmitting(true);
368
+ try {
369
+ const payload = {
370
+ kno: activeKno,
371
+ tenantId: tenantId,
372
+ newData: {
373
+ connectionDetails: connectionData,
374
+ addressDetails: addressData,
375
+ propertyInfo: propertyData,
376
+ meterDetails: meterData,
377
+ },
378
+ };
379
+
380
+ const result = await updateMutation.mutateAsync(payload);
381
+ if (result) {
382
+ history.push("/digit-ui/employee/ekyc/response", { success: true, result });
383
+ }
384
+ } catch (err) {
385
+ console.error("Submit Error:", err);
386
+ } finally {
387
+ setIsSubmitting(false);
388
+ }
389
+ };
390
+
391
+ const handleViewDocument = (fileStoreId) => {
392
+ if (!fileStoreId) return;
393
+ const documentUrl = `https://dev-djb.nitcon.in/filestore/v1/files/id?tenantId=dl.djb&fileStoreId=${fileStoreId}`;
394
+ setPreviewUrl(documentUrl);
395
+ setShowPreview(true);
396
+ };
397
+
398
+ if (isSearchLoading || isSubmitting) return <Loader />;
399
+
400
+ const baseUrl = "/digit-ui/employee/ekyc";
401
+
402
+ return (
403
+ <div className="employeeCard overflow-y-scroll">
404
+ <Card style={{ padding: "32px" }}>
405
+ {/* ── Header ───────────────────────────────────────────────────── */}
406
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "40px", borderBottom: "1px solid #EAECF0", paddingBottom: "20px" }}>
407
+ <CardHeader style={{ margin: 0, fontSize: "28px" }}>{t("EKYC_REVIEW_APPLICATION")}</CardHeader>
408
+ <div style={{
409
+ background: "#F9FAFB", border: "1px solid #EAECF0",
410
+ borderRadius: "24px", padding: "8px 20px",
411
+ fontSize: "14px", color: "#475467", fontWeight: "500"
412
+ }}>
413
+ {t("EKYC_K_NUMBER")}: <span style={{ color: "#101828", fontWeight: "700" }}>{activeKno}</span>
414
+ </div>
415
+ </div>
416
+
417
+ {/* ── 1. Connection Details ────────────────────────────────────── */}
418
+ <ReviewSection
419
+ title={t("EKYC_CONNECTION_DETAILS")}
420
+ fields={connectionFields}
421
+ newData={connectionData}
422
+ oldData={oldDataRaw?.connection}
423
+ t={t}
424
+ jumpTo={`${baseUrl}/consumer-details`}
425
+ state={{ ...flowState, reviewData: searchData, edits }}
426
+ />
427
+
428
+ {/* ── 2. Address Details ──────────────────────────────────────── */}
429
+ <ReviewSection
430
+ title={t("EKYC_ADDRESS_DETAILS")}
431
+ fields={addressFields}
432
+ newData={addressData}
433
+ oldData={oldDataRaw?.address}
434
+ t={t}
435
+ jumpTo={`${baseUrl}/address-details`}
436
+ state={{ ...flowState, reviewData: searchData, edits }}
437
+ />
438
+
439
+ {/* ── 3. Property Info ────────────────────────────────────────── */}
440
+ <ReviewSection
441
+ title={t("EKYC_PROPERTY_INFO")}
442
+ fields={propertyFields}
443
+ newData={propertyData}
444
+ oldData={oldDataRaw?.property}
445
+ t={t}
446
+ jumpTo={`${baseUrl}/property-info`}
447
+ state={{ ...flowState, reviewData: searchData, edits }}
448
+ />
449
+
450
+ {/* ── 4. Meter Details ────────────────────────────────────────── */}
451
+ <ReviewSection
452
+ title={t("EKYC_METER_DETAILS")}
453
+ fields={meterFields}
454
+ newData={meterData}
455
+ oldData={oldDataRaw?.meter}
456
+ t={t}
457
+ jumpTo={`${baseUrl}/meter-details`}
458
+ state={{ ...flowState, reviewData: searchData, edits }}
459
+ />
460
+
461
+ {/* ── 5. Documents ────────────────────────────────────────────── */}
462
+ <div style={{ marginTop: "40px" }}>
463
+ <CardSubHeader style={{ marginBottom: "20px" }}>{t("EKYC_DOCUMENTS")}</CardSubHeader>
464
+ <StatusTable style={{ maxWidth: "600px" }}>
465
+ <Row
466
+ label={t("EKYC_DOOR_PHOTO")}
467
+ text={addressData.doorPhotoFilestoreId ? t("EKYC_PHOTO_AVAILABLE") : t("CS_NA")}
468
+ actionButton={addressData.doorPhotoFilestoreId && (
469
+ <div onClick={() => handleViewDocument(addressData.doorPhotoFilestoreId)}>
470
+ <GenericFileIcon style={{ cursor: "pointer", fill: "#F47738" }} />
471
+ </div>
472
+ )}
473
+ />
474
+ <Row
475
+ label={t("EKYC_METER_PHOTO")}
476
+ text={meterData.meterPhotoFileStoreId ? t("EKYC_PHOTO_AVAILABLE") : t("CS_NA")}
477
+ actionButton={meterData.meterPhotoFileStoreId && (
478
+ <div onClick={() => handleViewDocument(meterData.meterPhotoFileStoreId)}>
479
+ <GenericFileIcon style={{ cursor: "pointer", fill: "#F47738" }} />
480
+ </div>
481
+ )}
482
+ />
483
+ <Row
484
+ label={t("EKYC_BUILDING_IMAGE")}
485
+ text={propertyData.buildingImageFileStoreId ? t("EKYC_PHOTO_AVAILABLE") : t("CS_NA")}
486
+ actionButton={propertyData.buildingImageFileStoreId && (
487
+ <div onClick={() => handleViewDocument(propertyData.buildingImageFileStoreId)}>
488
+ <GenericFileIcon style={{ cursor: "pointer", fill: "#F47738" }} />
489
+ </div>
490
+ )}
491
+ />
492
+ <Row
493
+ label={t("EKYC_PROPERTY_DOCUMENTS")}
494
+ text={propertyData.propertyDocumentFileStoreId ? t("EKYC_DOCS_AVAILABLE") : t("CS_NA")}
495
+ actionButton={propertyData.propertyDocumentFileStoreId && (
496
+ <div onClick={() => handleViewDocument(propertyData.propertyDocumentFileStoreId)}>
497
+ <GenericFileIcon style={{ cursor: "pointer", fill: "#F47738" }} />
498
+ </div>
499
+ )}
500
+ />
501
+ </StatusTable>
502
+ </div>
503
+
504
+ <div style={{ marginTop: "40px", paddingTop: "30px", borderTop: "1px solid #EAECF0" }}>
505
+ <CheckBox
506
+ id="agreeDeclaration"
507
+ name="agreeDeclaration"
508
+ label={<span style={{ fontSize: "16px", color: "#344054" }}>{t("EKYC_FINAL_DECLARATION")}</span>}
509
+ onChange={handleDeclaration}
510
+ checked={agree}
511
+ />
512
+ </div>
513
+ </Card>
514
+
515
+ <ActionBar style={{ position: "static", marginTop: "32px", display: "flex", justifyContent: "flex-end" }}>
516
+ <SubmitBar
517
+ label={t("EKYC_REJECT")}
518
+ onSubmit={handleReject}
519
+ disabled={!agree}
520
+ />
521
+ <SubmitBar
522
+ label={t("EKYC_APPROVE")}
523
+ onSubmit={handleApprove}
524
+ disabled={!agree}
525
+ />
526
+ <SubmitBar label={t("EKYC_SUBMIT_APPLICATION")} onSubmit={handleFinalSubmit} disabled={!agree} />
527
+ </ActionBar>
528
+
529
+ {/* ── Document Preview Modal ────────────────────────────────────── */}
530
+ {showPreview && (
531
+ <div style={{
532
+ position: "fixed", top: 0, left: 0, width: "100%", height: "100%",
533
+ backgroundColor: "rgba(0,0,0,0.7)", display: "flex", justifyContent: "center",
534
+ alignItems: "center", zIndex: 10000
535
+ }}>
536
+ <div style={{
537
+ position: "relative", backgroundColor: "#fff", padding: "30px",
538
+ borderRadius: "12px", maxWidth: "600px", width: "100%", maxHeight: "70vh", overflow: "auto",
539
+ boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"
540
+ }}>
541
+ <div
542
+ onClick={() => setShowPreview(false)}
543
+ style={{
544
+ position: "absolute", top: "10px", right: "10px", cursor: "pointer",
545
+ fontWeight: "bold", fontSize: "20px", color: "#333", background: "#eee",
546
+ width: "30px", height: "30px", display: "flex", justifyContent: "center",
547
+ alignItems: "center", borderRadius: "50%"
548
+ }}
549
+ >
550
+ ×
551
+ </div>
552
+ <img
553
+ src={previewUrl}
554
+ alt="Document Preview"
555
+ style={{ maxWidth: "100%", height: "auto", display: "block" }}
556
+ onError={(e) => {
557
+ e.target.style.display = "none";
558
+ e.target.nextSibling.style.display = "block";
559
+ }}
560
+ />
561
+ <div style={{ display: "none", padding: "40px", textAlign: "center" }}>
562
+ <p>{t("EKYC_DOCUMENT_PREVIEW_NOT_AVAILABLE")}</p>
563
+ <LinkButton label={t("EKYC_OPEN_IN_NEW_TAB")} onClick={() => window.open(previewUrl, "_blank")} />
564
+ </div>
565
+ </div>
566
+ </div>
567
+ )}
568
+ </div>
569
+ );
570
+ };
571
+
572
+ export default Review;
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { Controller } from "react-hook-form";
3
- import { CardLabelError, TextInput, Tooltip, Label } from "@djb25/digit-ui-react-components";
3
+ import { CardLabelError, TextInput, CustomTooltip, Label } from "@djb25/digit-ui-react-components";
4
4
  import { useTranslation } from "react-i18next";
5
5
 
6
6
  const SearchFormFieldsComponents = ({ searchFormState, controlSearchForm }) => {
@@ -13,7 +13,7 @@ const SearchFormFieldsComponents = ({ searchFormState, controlSearchForm }) => {
13
13
  <span className="mobile-input">
14
14
  <Label className="flex-roww flex-gap-2">
15
15
  {t("EKYC_K_NUMBER") || "K Number"}
16
- <Tooltip message={t("EKYC_K_NUMBER_MESSAGE")} />
16
+ <CustomTooltip message={t("EKYC_K_NUMBER_MESSAGE")} />
17
17
  </Label>
18
18
 
19
19
  <Controller
@@ -36,7 +36,7 @@ const SearchFormFieldsComponents = ({ searchFormState, controlSearchForm }) => {
36
36
  {/* <span className="mobile-input">
37
37
  <Label className="flex-roww flex-gap-2">
38
38
  {t("EKYC_K_NAME") || "K Name"}
39
- <Tooltip message={t("EKYC_K_NAME_MESSAGE")} />
39
+ <CustomTooltip message={t("EKYC_K_NAME_MESSAGE")} />
40
40
  </Label>
41
41
 
42
42
  <Controller