@djb25/digit-ui-module-ekyc 1.0.4 → 1.0.6

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.
@@ -4,13 +4,7 @@ import DesktopInbox from "../../components/DesktopInbox";
4
4
  import MobileInbox from "../../components/MobileInbox";
5
5
  import Filter from "../../components/Filter";
6
6
 
7
- const MOCK_DATA_ITEMS = [
8
- { applicationNumber: "EKYC-2024-001", citizenName: "Rahul Sharma", mobileNumber: "9876543210", status: "COMPLETED" },
9
- { applicationNumber: "EKYC-2024-002", citizenName: "Anjali Devi", mobileNumber: "9123456789", status: "PENDING" },
10
- { applicationNumber: "EKYC-2024-003", citizenName: "Amit Kumar", mobileNumber: "8888888888", status: "REJECTED" },
11
- { applicationNumber: "EKYC-2024-004", citizenName: "Priya Singh", mobileNumber: "7777777777", status: "COMPLETED" },
12
- { applicationNumber: "EKYC-2024-005", citizenName: "Suresh Gupta", mobileNumber: "6666666666", status: "PENDING" },
13
- ];
7
+ // Mock data removed in favor of API integration
14
8
 
15
9
  const Inbox = ({
16
10
  parentRoute,
@@ -33,28 +27,40 @@ const Inbox = ({
33
27
  // Maintain the full search objects for the Search component
34
28
  const [searchParams, setSearchParams] = useState(initialStates.searchParams || { status: defaultStatusOption });
35
29
 
36
- // 2. Local Filtering Logic for Static Data
37
- const filteredStaticData = useMemo(() => {
38
- return MOCK_DATA_ITEMS.filter((item) => {
39
- let match = true;
40
- // Extract the string value from the status object if it exists
41
- const currentStatus = searchParams.status?.value !== undefined ? searchParams.status.value : searchParams.status;
42
-
43
- if (currentStatus && item.status !== currentStatus) {
44
- match = false;
45
- }
46
- return match;
47
- });
48
- }, [searchParams]);
49
-
50
- const staticCountData = useMemo(() => {
30
+ // 2. API Data Fetching
31
+ const { isLoading, data: dashboardData, isFetching } = Digit.Hooks.ekyc.useEkycSurveyorDashboard(
32
+ {},
33
+ {
34
+ tenantId,
35
+ offset: pageOffset,
36
+ limit: pageSize,
37
+ status: searchParams.status?.value || ""
38
+ },
39
+ {
40
+ enabled: !!tenantId,
41
+ }
42
+ );
43
+
44
+ const filteredData = useMemo(() => {
45
+ const items = dashboardData?.dashboardInfo?.consumerList || [];
46
+ return items.map(item => ({
47
+ ...item,
48
+ applicationNumber: item.kno || item.applicationNumber,
49
+ citizenName: item.consumerName || item.citizenName,
50
+ }));
51
+ }, [dashboardData]);
52
+
53
+ const countData = useMemo(() => {
54
+ const info = dashboardData?.dashboardInfo || {};
51
55
  return {
52
- total: MOCK_DATA_ITEMS.length,
53
- completed: MOCK_DATA_ITEMS.filter(i => i.status === "COMPLETED").length,
54
- pending: MOCK_DATA_ITEMS.filter(i => i.status === "PENDING").length,
55
- rejected: MOCK_DATA_ITEMS.filter(i => i.status === "REJECTED").length
56
+ total: info.total || 0,
57
+ completed: info.completed || 0,
58
+ pending: info.pending || 0,
59
+ rejected: info.rejected || 0
56
60
  };
57
- }, []);
61
+ }, [dashboardData]);
62
+
63
+ const totalRecords = dashboardData?.dashboardInfo?.totalRecords || dashboardData?.totalCount || 0;
58
64
 
59
65
  // 3. Handlers
60
66
  const handleSearch = useCallback((filterParam) => {
@@ -84,10 +90,9 @@ const Inbox = ({
84
90
  name: "status",
85
91
  type: "dropdown",
86
92
  options: [
87
- { label: t("CHOOSE_STATUS"), value: "" },
88
- { label: t("EKYC_STATUS_COMPLETED"), value: "COMPLETED" },
89
- { label: t("EKYC_STATUS_PENDING"), value: "PENDING" },
90
- { label: t("EKYC_STATUS_REJECTED"), value: "REJECTED" },
93
+ { label: t("EKYC_STATUS_ALL"), value: "" },
94
+ { label: t("EKYC_STATUS_ACTIVE"), value: "ACTIVE" },
95
+ { label: t("EKYC_STATUS_PENDING"), value: "PENDING START" },
91
96
  ],
92
97
  optionsKey: "label"
93
98
  },
@@ -98,19 +103,19 @@ const Inbox = ({
98
103
  <div className="inbox-main-container">
99
104
  {Digit.Utils.browser.isMobile() ? (
100
105
  <MobileInbox
101
- data={{ items: filteredStaticData, totalCount: filteredStaticData.length }}
102
- isLoading={false}
106
+ data={{ items: filteredData, totalCount: totalRecords }}
107
+ isLoading={isLoading || isFetching}
103
108
  onSearch={handleSearch}
104
109
  searchFields={searchFields}
105
110
  searchParams={searchParams}
106
111
  parentRoute={parentRoute}
107
- countData={staticCountData}
112
+ countData={countData}
108
113
  />
109
114
  ) : (
110
115
  <DesktopInbox
111
116
  businessService={businessService}
112
- data={{ items: filteredStaticData, totalCount: filteredStaticData.length }}
113
- isLoading={false}
117
+ data={{ items: filteredData, totalCount: totalRecords }}
118
+ isLoading={isLoading || isFetching}
114
119
  searchFields={searchFields}
115
120
  onSearch={handleSearch}
116
121
  onSort={handleSort}
@@ -122,8 +127,8 @@ const Inbox = ({
122
127
  parentRoute={parentRoute}
123
128
  searchParams={searchParams}
124
129
  sortParams={sortParams}
125
- totalRecords={filteredStaticData.length}
126
- countData={staticCountData}
130
+ totalRecords={totalRecords}
131
+ countData={countData}
127
132
  filterComponent="EKYC_INBOX_FILTER"
128
133
  />
129
134
  )}
@@ -366,20 +366,34 @@ const PropertyInfo = () => {
366
366
  </div>
367
367
  <input type="file" ref={fileRef} accept=".pdf" style={{ display: "none" }} onChange={handleFileUpload} />
368
368
  <div
369
- onClick={() => fileRef.current.click()}
370
- onMouseOver={(e) => e.currentTarget.style.borderColor = "#185FA5"}
371
- onMouseOut={(e) => e.currentTarget.style.borderColor = "#B5D4F4"}
369
+ onClick={() => pidNumber && fileRef.current.click()}
370
+ onMouseOver={(e) => { if (pidNumber) e.currentTarget.style.borderColor = "#185FA5"; }}
371
+ onMouseOut={(e) => { if (pidNumber) e.currentTarget.style.borderColor = "#B5D4F4"; }}
372
372
  style={{
373
- border: "1.5px dashed #B5D4F4", borderRadius: "10px",
374
- padding: "28px 20px", textAlign: "center", cursor: "pointer",
375
- backgroundColor: "#E6F1FB", minHeight: "160px",
376
- display: "flex", flexDirection: "column",
377
- alignItems: "center", justifyContent: "center", gap: "10px",
378
- transition: "border-color 0.15s",
373
+ border: pidNumber ? "1.5px dashed #B5D4F4" : "1.5px dashed #D0D5DD",
374
+ borderRadius: "10px",
375
+ padding: "28px 20px",
376
+ textAlign: "center",
377
+ cursor: pidNumber ? "pointer" : "not-allowed",
378
+ backgroundColor: pidNumber ? "#E6F1FB" : "#F9FAFB",
379
+ minHeight: "160px",
380
+ display: "flex",
381
+ flexDirection: "column",
382
+ alignItems: "center",
383
+ justifyContent: "center",
384
+ gap: "10px",
385
+ transition: "all 0.15s",
386
+ opacity: pidNumber ? 1 : 0.6,
379
387
  }}
380
388
  >
381
- <div style={{ background: "#fff", padding: "10px", borderRadius: "10px", display: "flex" }}>
382
- <svg width="32" height="32" viewBox="0 0 24 24" fill="#185FA5">
389
+ <div style={{
390
+ background: pidNumber ? "#fff" : "#EAECF0",
391
+ padding: "10px",
392
+ borderRadius: "10px",
393
+ display: "flex",
394
+ filter: pidNumber ? "none" : "grayscale(100%)"
395
+ }}>
396
+ <svg width="32" height="32" viewBox="0 0 24 24" fill={pidNumber ? "#185FA5" : "#98A2B3"}>
383
397
  <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13z" />
384
398
  <path d="M12 18v-4M12 14l-2 2M12 14l2 2" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" />
385
399
  </svg>
@@ -390,10 +404,10 @@ const PropertyInfo = () => {
390
404
  </div>
391
405
  ) : (
392
406
  <>
393
- <div style={{ fontSize: "13px", fontWeight: "600", color: "#185FA5" }}>
394
- {t("EKYC_UPLOAD_PROPERTY_DOC_CTA") || "Tap to upload"}
407
+ <div style={{ fontSize: "13px", fontWeight: "600", color: pidNumber ? "#185FA5" : "#98A2B3" }}>
408
+ {pidNumber ? (t("EKYC_UPLOAD_PROPERTY_DOC_CTA") || "Tap to upload") : (t("EKYC_ENTER_PID_FIRST_CTA") || "Enter PID to upload")}
395
409
  </div>
396
- <div style={{ fontSize: "12px", color: "#378ADD" }}>PDF only</div>
410
+ <div style={{ fontSize: "12px", color: pidNumber ? "#378ADD" : "#98A2B3" }}>{pidNumber ? "PDF | Max 5MB" : "Requires PID"}</div>
397
411
  </>
398
412
  )}
399
413
  </div>
@@ -431,6 +445,9 @@ const PropertyInfo = () => {
431
445
  <div style={{ fontSize: "12px", color: "#667085", marginTop: "2px" }}>
432
446
  {t("EKYC_BUILDING_PHOTO") || "Building photo with GPS"}
433
447
  </div>
448
+ <div style={{ fontSize: "11px", color: "#98A2B3", marginTop: "2px" }}>
449
+ JPG, PNG | Max 2MB
450
+ </div>
434
451
  </>
435
452
  ) : (
436
453
  <>
@@ -1,10 +1,11 @@
1
- import React, { Fragment } from "react";
1
+ import React, { Fragment, useState } from "react";
2
2
  import {
3
3
  Card,
4
4
  CardHeader,
5
5
  SubmitBar,
6
6
  HomeIcon,
7
7
  ActionBar,
8
+ Toast,
8
9
  } from "@djb25/digit-ui-react-components";
9
10
  import { useTranslation } from "react-i18next";
10
11
  import { useHistory, useLocation } from "react-router-dom";
@@ -133,8 +134,89 @@ const Review = () => {
133
134
  propertyDetails = {},
134
135
  } = location.state || {};
135
136
 
136
- const handleSubmit = () => {
137
- history.push("/digit-ui/employee/ekyc/dashboard");
137
+ const [isSubmitting, setIsSubmitting] = useState(false);
138
+ const [toast, setToast] = useState(null);
139
+
140
+ // ── Helper: upload a File object to Filestore ──────────────────────────────
141
+ const uploadFile = async (file, tenantId) => {
142
+ if (!file) return null;
143
+ const res = await Digit.UploadServices.Filestorage("EKYC", file, tenantId);
144
+ return res?.data?.files?.[0]?.fileStoreId || null;
145
+ };
146
+
147
+ // ── Helper: convert a data-URL (base64 string) to a File blob ─────────────
148
+ const dataUrlToFile = (dataUrl, filename) => {
149
+ const arr = dataUrl.split(",");
150
+ const mime = arr[0].match(/:(.*?);/)[1];
151
+ const bstr = atob(arr[1]);
152
+ let n = bstr.length;
153
+ const u8arr = new Uint8Array(n);
154
+ while (n--) u8arr[n] = bstr.charCodeAt(n);
155
+ return new File([u8arr], filename, { type: mime });
156
+ };
157
+
158
+ const handleSubmit = async () => {
159
+ setIsSubmitting(true);
160
+ setToast(null);
161
+ try {
162
+ const tenantId = Digit.ULBService.getCurrentTenantId() || "dl.djb";
163
+ const userInfo = Digit.UserService.getUser()?.info || {};
164
+
165
+ // ── 1. Upload property document (PDF File object) ──────────────────────
166
+ const propertyDocFile = propertyDetails.propertyDocument || null; // File object from <input>
167
+ const propertyDocFileStoreId = await uploadFile(propertyDocFile, tenantId);
168
+
169
+ // ── 2. Upload building photo (data-URL string from camera capture) ─────
170
+ let buildingImageFileStoreId = null;
171
+ if (propertyDetails.buildingPhoto) {
172
+ const photoFile = dataUrlToFile(propertyDetails.buildingPhoto, "building_photo.jpg");
173
+ buildingImageFileStoreId = await uploadFile(photoFile, tenantId);
174
+ }
175
+
176
+ // ── 3. Build request payload ───────────────────────────────────────────
177
+ const requestBody = {
178
+ RequestInfo: {
179
+ apiId: "Rainmaker",
180
+ ver: "1.0",
181
+ msgId: `${Date.now()}|${navigator.language || "en_IN"}`,
182
+ tenantId,
183
+ authToken: userInfo.access_token || Digit.UserService.getUser()?.access_token || "",
184
+ },
185
+ updateType: "PROPERTY",
186
+ kno: kNumber,
187
+ pidNumber: propertyDetails.pidNumber || null,
188
+ propertyDocumentFileStoreId: propertyDocFileStoreId,
189
+ buildingImageFileStoreId: buildingImageFileStoreId,
190
+ userType: propertyDetails.userType?.value || null,
191
+ noOfFloor: propertyDetails.noOfFloors?.value ? parseInt(propertyDetails.noOfFloors.value, 10) : null,
192
+ typeOfConnection: propertyDetails.connectionCategory?.value || null,
193
+ connectionCategory: propertyDetails.connectionType?.value || null,
194
+ modifiedBy: userInfo.name || userInfo.userName || null,
195
+ };
196
+
197
+ // ── 4. Call the update API ─────────────────────────────────────────────
198
+ await Digit.CustomService.getResponse({
199
+ url: "/ekyc-service/user/application/_update",
200
+ params: { tenantId },
201
+ body: requestBody,
202
+ useCache: false,
203
+ method: "POST",
204
+ });
205
+
206
+ setToast({ type: "success", message: t("EKYC_SUBMIT_SUCCESS") || "Application submitted successfully!" });
207
+ setTimeout(() => {
208
+ history.push("/digit-ui/employee/ekyc/dashboard");
209
+ }, 1800);
210
+ } catch (err) {
211
+ console.error("eKYC Submit Error:", err);
212
+ setToast({
213
+ type: "error",
214
+ message: err?.response?.data?.Errors?.[0]?.message ||
215
+ t("EKYC_SUBMIT_ERROR") || "Submission failed. Please try again.",
216
+ });
217
+ } finally {
218
+ setIsSubmitting(false);
219
+ }
138
220
  };
139
221
 
140
222
  const handleEditAadhaar = () => {
@@ -150,6 +232,7 @@ const Review = () => {
150
232
  };
151
233
 
152
234
  return (
235
+ <Fragment>
153
236
  <div className="inbox-container">
154
237
  <style>{`
155
238
  @keyframes fadeSlideIn {
@@ -259,7 +342,7 @@ const Review = () => {
259
342
  editLabel={t("CS_COMMON_EDIT") || "Edit"}
260
343
  rows={[
261
344
  { label: t("EKYC_NAME") || "Name", value: aadhaarDetails.userName || "Rajesh Kumar Singh" },
262
- { label: t("EKYC_AADHAAR") || "Aadhaar no.", value: aadhaarDetails.aadhaarLastFour ? `XXXX XXXX ${aadhaarDetails.aadhaarLastFour}` : "XXXX XXXX 1234" },
345
+ { label: t("EKYC_AADHAAR") || "Aadhaar no.", value: aadhaarDetails.aadhaarLastFour ? `${aadhaarDetails.aadhaarLastFour}` : "XXXX XXXX 1234" },
263
346
  { label: t("EKYC_MOBILE_NO") || "Mobile no.", value: aadhaarDetails.mobileNumber || "XXXXXXXXXX" },
264
347
  { label: t("EKYC_EMAIL_ADDRESS") || "Email", value: aadhaarDetails.email || null },
265
348
  ]}
@@ -317,8 +400,12 @@ const Review = () => {
317
400
  {/* Submit (Non-sticky, at form end) */}
318
401
  <div style={{ marginTop: "24px" }}>
319
402
  <SubmitBar
320
- label={t("ES_COMMON_SUBMIT") || "Submit"}
403
+ label={isSubmitting
404
+ ? (t("EKYC_SUBMITTING") || "Submitting...")
405
+ : (t("ES_COMMON_SUBMIT") || "Submit")
406
+ }
321
407
  onSubmit={handleSubmit}
408
+ disabled={isSubmitting}
322
409
  />
323
410
  </div>
324
411
 
@@ -338,6 +425,17 @@ const Review = () => {
338
425
  </Card>
339
426
  </div>
340
427
  </div>
428
+
429
+ {/* Toast notification */}
430
+ {toast && (
431
+ <Toast
432
+ label={toast.message}
433
+ error={toast.type === "error"}
434
+ success={toast.type === "success"}
435
+ onClose={() => setToast(null)}
436
+ />
437
+ )}
438
+ </Fragment>
341
439
  );
342
440
  };
343
441