@djb25/digit-ui-module-ekyc 1.0.8 → 1.0.9

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.
@@ -0,0 +1,117 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { useQueryClient } from "react-query";
4
+ import { Redirect, Route, Switch, useHistory, useLocation, useRouteMatch } from "react-router-dom";
5
+ import { Header, VerticalTimeline } from "@djb25/digit-ui-react-components";
6
+ import { ekycConfig } from "../../config/config";
7
+
8
+ const EKYCForm = ({ path: passedPath }) => {
9
+ const queryClient = useQueryClient();
10
+ const match = useRouteMatch();
11
+ const { t } = useTranslation();
12
+ const location = useLocation();
13
+ const { pathname } = location;
14
+ const history = useHistory();
15
+
16
+ let config = [];
17
+ const [params, setParams, clearParams] = Digit.Hooks.useSessionStorage("EKYC_CREATE", {});
18
+ const userInfo = Digit.UserService.getUser();
19
+ const tenantId = Digit.ULBService.getCurrentTenantId();
20
+
21
+ useEffect(() => {
22
+ if (location.state && Object.keys(location.state).length > 0) {
23
+ const { edits, ...rest } = location.state;
24
+ setParams({ ...params, ...rest, ...edits });
25
+ }
26
+ }, [location.state]);
27
+
28
+ const goNext = (skipStep, index, isAddMultiple, key) => {
29
+ let currentPath = pathname.split("/").pop();
30
+ let routeObj = config.find((routeObj) => (key ? routeObj.key === key : routeObj.route === currentPath));
31
+ if (!routeObj) routeObj = config.find((routeObj) => routeObj.route === currentPath);
32
+
33
+ let nextStep = null;
34
+ const currentIndex = config.findIndex(c => c.route === routeObj.route);
35
+ if (currentIndex > -1 && currentIndex < config.length - 1) {
36
+ nextStep = config[currentIndex + 1].route;
37
+ }
38
+
39
+ let redirectWithHistory = history.push;
40
+ if (skipStep) {
41
+ redirectWithHistory = history.replace;
42
+ }
43
+
44
+ const base = passedPath || match.path.split('/').slice(0, -1).join('/');
45
+ if (nextStep === null) {
46
+ return redirectWithHistory(`${base}/review`, { ...params, edits: params });
47
+ }
48
+
49
+ redirectWithHistory(`${base}/${nextStep}`, { ...params });
50
+ };
51
+
52
+ function handleSelect(key, data, skipStep, index, isAddMultiple = false) {
53
+ setParams({ ...params, ...{ [key]: { ...params[key], ...data } } });
54
+ goNext(skipStep, index, isAddMultiple, key);
55
+ }
56
+
57
+ const onSuccess = () => {
58
+ clearParams();
59
+ queryClient.invalidateQueries("EKYC_CREATE");
60
+ };
61
+
62
+ ekycConfig.forEach((obj) => {
63
+ config = config.concat(obj.body);
64
+ });
65
+
66
+ config.indexRoute = "consumer-details";
67
+
68
+ const formStepRoutes = config.map(c => c.route);
69
+ const isFormStep = formStepRoutes.some((route) => pathname.includes(route));
70
+
71
+ const sectionRefs = useRef({});
72
+
73
+ useEffect(() => {
74
+ if (isFormStep) {
75
+ const currentRoute = pathname.split("/").pop();
76
+ if (sectionRefs.current[currentRoute]) {
77
+ sectionRefs.current[currentRoute].scrollIntoView({ behavior: "smooth", block: "start" });
78
+ }
79
+ }
80
+ }, [pathname, isFormStep]);
81
+
82
+ return (
83
+ <React.Fragment>
84
+ <div className="employee-form-section-wrapper">
85
+ <VerticalTimeline config={config} showFinalStep={true} />
86
+ <div className="employee-form-section">
87
+ <Switch>
88
+ <Route path={formStepRoutes.map((route) => `${(passedPath || match.path.split('/').slice(0, -1).join('/'))}/${route}`)}>
89
+ <div className="single-page-form-container">
90
+ {config.map((routeObj, index) => {
91
+ const { component, key } = routeObj;
92
+ const Component = typeof component === "string" ? Digit.ComponentRegistryService.getComponent(component) : component;
93
+
94
+ return (
95
+ <div key={index} ref={(el) => (sectionRefs.current[routeObj.route] = el)} className="form-section-unit">
96
+ <Component
97
+ config={{ ...routeObj, isCollapsible: true, defaultOpen: true }}
98
+ onSelect={handleSelect}
99
+ t={t}
100
+ formData={params}
101
+ />
102
+ </div>
103
+ );
104
+ })}
105
+ </div>
106
+ </Route>
107
+ <Route>
108
+ <Redirect to={`${(passedPath || match.path.split('/').slice(0, -1).join('/'))}/${config.indexRoute}`} />
109
+ </Route>
110
+ </Switch>
111
+ </div>
112
+ </div>
113
+ </React.Fragment>
114
+ );
115
+ };
116
+
117
+ export default EKYCForm;
@@ -1,147 +1,255 @@
1
- import React, { useCallback, useEffect, useState, useMemo } from "react";
2
- import { useTranslation } from "react-i18next";
3
- import DesktopInbox from "../../components/DesktopInbox";
4
- import MobileInbox from "../../components/MobileInbox";
5
- import Filter from "../../components/Filter";
1
+ import React, { useMemo, useCallback, useReducer } from "react";
2
+ import { useLocation } from "react-router-dom";
3
+ import { InboxComposer } from "@djb25/digit-ui-react-components";
4
+ import useInboxTableConfig from "../../hook/useInboxTableConfig";
5
+ import SearchFormFieldsComponents from "../../components/SearchFormFieldsComponent";
6
6
 
7
7
  // Mock data removed in favor of API integration
8
8
 
9
- const Inbox = ({
10
- parentRoute,
11
- businessService = "EKYC",
12
- initialStates = {},
13
- filterComponent,
14
- isInbox,
15
- }) => {
16
- const tenantId = Digit.ULBService.getCurrentTenantId();
17
- const { t } = useTranslation();
18
-
19
- // 1. Unified State Management
20
- const [pageOffset, setPageOffset] = useState(initialStates.pageOffset || 0);
21
- const [pageSize, setPageSize] = useState(initialStates.pageSize || 10);
22
- const [sortParams, setSortParams] = useState(initialStates.sortParams || [{ id: "createdTime", desc: true }]);
23
-
24
- // Define the default option for the dropdown
25
- const defaultStatusOption = useMemo(() => ({ label: t("EKYC_STATUS_ALL"), value: "" }), [t]);
26
-
27
- // Maintain the full search objects for the Search component
28
- const [searchParams, setSearchParams] = useState(initialStates.searchParams || { status: defaultStatusOption });
29
-
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
- },
38
- {
39
- enabled: !!tenantId,
40
- }
41
- );
42
-
43
- const filteredData = useMemo(() => {
44
- let items = dashboardData?.dashboardInfo?.consumerList || [];
45
-
46
- // Frontend filtering since we no longer send status to API
47
- const selectedStatus = searchParams.status?.value;
48
- if (selectedStatus && selectedStatus !== "") {
49
- items = items.filter(item => item.status === selectedStatus);
50
- }
51
-
52
- return items.map(item => ({
53
- ...item,
54
- applicationNumber: item.kno || item.applicationNumber,
55
- citizenName: item.consumerName || item.citizenName,
56
- }));
57
- }, [dashboardData, searchParams.status]);
58
-
59
- const countData = useMemo(() => {
60
- const info = dashboardData?.dashboardInfo || {};
9
+ const Inbox = ({ parentRoute, businessService = "EKYC", initialStates = {}, filterComponent, isInbox }) => {
10
+ const tenantId = Digit.ULBService.getCurrentTenantId();
11
+ const location = useLocation();
12
+
13
+ const formInitValue = {
14
+ filterForm: {},
15
+ searchForm: {},
16
+ tableForm: {
17
+ limit: 10,
18
+ offset: 0,
19
+ sortBy: "createdTime",
20
+ sortOrder: "DESC",
21
+ },
22
+ };
23
+
24
+ const [formState, dispatch] = useReducer(formReducer, formInitValue);
25
+
26
+ const queryParams = useMemo(() => {
27
+ return {
28
+ tenantId,
29
+ offset: formState?.tableForm?.offset || 0,
30
+ limit: formState?.tableForm?.limit || 10,
31
+ search: formState?.searchForm || {},
32
+ };
33
+ }, [tenantId, formState?.tableForm?.offset, formState?.tableForm?.limit, formState?.searchForm]);
34
+
35
+ const { isLoading, data: dashboardData = {} } = Digit.Hooks.ekyc.useEkycSurveyorDashboard({}, queryParams, {
36
+ enabled: !!tenantId,
37
+ keepPreviousData: true,
38
+ });
39
+
40
+ const searchDetails = useMemo(
41
+ () => ({
42
+ kno: formState?.searchForm?.kNumber || "",
43
+ name: formState?.searchForm?.kName || "",
44
+ }),
45
+ [formState?.searchForm?.kNumber, formState?.searchForm?.kName]
46
+ );
47
+
48
+ const isSearchActive = !!(searchDetails.kno || searchDetails.name);
49
+
50
+ const { isLoading: isSearchLoading, data: searchData } = Digit.Hooks.ekyc.useSearchConnection(
51
+ {
52
+ tenantId,
53
+ details: searchDetails,
54
+ },
55
+ {
56
+ enabled: !!tenantId && !!searchDetails.kno, // 🔥 important
57
+ keepPreviousData: true,
58
+ }
59
+ );
60
+
61
+ const sourceData = useMemo(() => {
62
+ if (isSearchActive) {
63
+ if (!searchData) return [];
64
+ return [searchData];
65
+ }
66
+
67
+ return dashboardData?.dashboardInfo?.consumerList || [];
68
+ }, [isSearchActive, searchData, dashboardData]);
69
+
70
+ const filteredData = useMemo(() => {
71
+ return (sourceData || []).map((item) => {
72
+ // ✅ detect search response
73
+ const isSearchItem = !!item.connectionDetails;
74
+
75
+ if (isSearchItem) {
61
76
  return {
62
- total: info.total || 0,
63
- completed: info.completed || 0,
64
- pending: info.pending || 0,
65
- rejected: info.rejected || 0,
66
- active: info.active || 0,
77
+ applicationNo: item.propertyInfo?.kno || "",
78
+ connectionNo: item.propertyInfo?.kno || "",
79
+ owner: item.connectionDetails?.consumerName || "",
80
+ applicationNumber: item.propertyInfo?.kno || "",
81
+ citizenName: item.connectionDetails?.consumerName || "",
82
+ status: item.connectionDetails?.statusflag || "",
83
+ sla: 0,
67
84
  };
68
- }, [dashboardData]);
69
-
70
- const totalRecords = dashboardData?.dashboardInfo?.totalRecords || dashboardData?.totalCount || 0;
71
-
72
- // 3. Handlers
73
- const handleSearch = useCallback((filterParam) => {
74
- // Here we keep the full objects (like for dropdowns) in searchParams
75
- // so that the Search component can display them correctly.
76
- setSearchParams((prev) => ({ ...prev, ...filterParam }));
77
- setPageOffset(0);
78
- }, []);
79
-
80
- const fetchNextPage = () => setPageOffset((prev) => prev + pageSize);
81
- const fetchPrevPage = () => setPageOffset((prev) => Math.max(prev - pageSize, 0));
82
-
83
- const handlePageSizeChange = (e) => {
84
- const newSize = Number(e.target.value);
85
- setPageSize(newSize);
86
- setPageOffset(0);
87
- };
88
-
89
- const handleSort = useCallback((args) => {
90
- if (args.length > 0) setSortParams(args);
91
- }, []);
92
-
93
- // 4. Form Configuration
94
- const searchFields = useMemo(() => [
95
- {
96
- label: t("EKYC_STATUS"),
97
- name: "status",
98
- type: "dropdown",
99
- options: [
100
- { label: t("EKYC_STATUS_ALL"), value: "" },
101
- { label: t("EKYC_STATUS_ACTIVE"), value: "ACTIVE" },
102
- { label: t("EKYC_STATUS_PENDING"), value: "PENDING START" },
103
- ],
104
- optionsKey: "label"
105
- },
106
- ], [t]);
107
-
108
- return (
109
- <div className="ekyc-employee-container">
110
- <div className="inbox-main-container">
111
- {Digit.Utils.browser.isMobile() ? (
112
- <MobileInbox
113
- data={{ items: filteredData, totalCount: totalRecords }}
114
- isLoading={isLoading || isFetching}
115
- onSearch={handleSearch}
116
- searchFields={searchFields}
117
- searchParams={searchParams}
118
- parentRoute={parentRoute}
119
- countData={countData}
120
- />
121
- ) : (
122
- <DesktopInbox
123
- businessService={businessService}
124
- data={{ items: filteredData, totalCount: totalRecords }}
125
- isLoading={isLoading || isFetching}
126
- searchFields={searchFields}
127
- onSearch={handleSearch}
128
- onSort={handleSort}
129
- onNextPage={fetchNextPage}
130
- onPrevPage={fetchPrevPage}
131
- currentPage={Math.floor(pageOffset / pageSize)}
132
- pageSizeLimit={pageSize}
133
- onPageSizeChange={handlePageSizeChange}
134
- parentRoute={parentRoute}
135
- searchParams={searchParams}
136
- sortParams={sortParams}
137
- totalRecords={totalRecords}
138
- countData={countData}
139
- filterComponent="EKYC_INBOX_FILTER"
140
- />
141
- )}
142
- </div>
143
- </div>
144
- );
85
+ }
86
+
87
+ // dashboard mapping
88
+ return {
89
+ ...item,
90
+ applicationNo: item.kno || item.applicationNumber || "",
91
+ connectionNo: item.connectionNo || "",
92
+ owner: item.consumerName || item.citizenName || "",
93
+ applicationNumber: item.kno || item.applicationNumber || "",
94
+ citizenName: item.consumerName || item.citizenName || "",
95
+ status: item.status || "",
96
+ sla: item.sla ?? 0,
97
+ };
98
+ });
99
+ }, [sourceData]);
100
+
101
+ const totalRecords = dashboardData?.dashboardInfo?.totalRecords || dashboardData?.totalCount || 0;
102
+
103
+ const checkPathName = location.pathname.includes("ekyc/inbox");
104
+ const PropsForInboxLinks = {
105
+ headerText: checkPathName ? "MODULE_WATER" : "MODULE_SW",
106
+ };
107
+
108
+ const SearchFormFields = useCallback(
109
+ ({ registerRef, searchFormState, controlSearchForm }) => (
110
+ <SearchFormFieldsComponents {...{ registerRef, searchFormState, controlSearchForm }} className="search" />
111
+ ),
112
+ []
113
+ );
114
+
115
+ const tableOrderFormDefaultValues = {
116
+ sortBy: "createdTime",
117
+ limit: window.Digit.Utils.browser.isMobile() ? 50 : 10,
118
+ offset: 0,
119
+ sortOrder: "DESC",
120
+ };
121
+
122
+ const onSearchFormSubmit = (data) => {
123
+ data.hasOwnProperty("") && delete data?.[""];
124
+ dispatch({ action: "mutateTableForm", data: { ...tableOrderFormDefaultValues }, checkPathName });
125
+ dispatch({ action: "mutateSearchForm", data, checkPathName });
126
+ };
127
+
128
+ const searchFormDefaultValues = {
129
+ mobileNumber: "",
130
+ applicationNumber: "",
131
+ consumerNo: "",
132
+ };
133
+
134
+ const onSearchFormReset = (setSearchFormValue) => {
135
+ setSearchFormValue("mobileNumber", null);
136
+ setSearchFormValue("applicationNumber", null);
137
+ setSearchFormValue("consumerNo", null);
138
+ dispatch({ action: "mutateSearchForm", data: searchFormDefaultValues });
139
+ };
140
+
141
+ const propsForSearchForm = {
142
+ SearchFormFields,
143
+ onSearchFormSubmit,
144
+ searchFormDefaultValues: formState?.searchForm,
145
+ resetSearchFormDefaultValues: searchFormDefaultValues,
146
+ onSearchFormReset,
147
+ className: "search-form-wns-inbox",
148
+ };
149
+
150
+ const FilterFormFields = useCallback(
151
+ ({ registerRef, controlFilterForm, setFilterFormValue, getFilterFormValue }) => <React.Fragment></React.Fragment>,
152
+ []
153
+ );
154
+
155
+ const propsForFilterForm = {
156
+ FilterFormFields,
157
+ onFilterFormSubmit: () => {},
158
+ filterFormDefaultValues: "",
159
+ resetFilterFormDefaultValues: "",
160
+ onFilterFormReset: () => {},
161
+ };
162
+
163
+ function formReducer(state, payload) {
164
+ const storageKey = payload.checkPathName ? "EKYC.INBOX" : "EKYC.SW.INBOX";
165
+
166
+ // ✅ safety for SLA
167
+ switch (payload.action) {
168
+ case "mutateSearchForm":
169
+ Digit.SessionStorage.set(storageKey, { ...state, searchForm: payload.data });
170
+ return { ...state, searchForm: payload.data };
171
+
172
+ case "mutateFilterForm":
173
+ Digit.SessionStorage.set(storageKey, { ...state, filterForm: payload.data });
174
+ return { ...state, filterForm: payload.data };
175
+
176
+ case "mutateTableForm":
177
+ Digit.SessionStorage.set(storageKey, { ...state, tableForm: payload.data });
178
+ return { ...state, tableForm: payload.data };
179
+
180
+ default:
181
+ return state; // ✅ IMPORTANT
182
+ }
183
+ }
184
+
185
+ const onPageSizeChange = (e) => {
186
+ const newLimit = Number(e.target.value);
187
+
188
+ dispatch({
189
+ action: "mutateTableForm",
190
+ data: {
191
+ ...formState.tableForm,
192
+ limit: newLimit,
193
+ offset: 0, // reset page
194
+ },
195
+ checkPathName,
196
+ });
197
+ };
198
+
199
+ const onSortingByData = (e) => {
200
+ if (e.length > 0) {
201
+ const [{ id, desc }] = e;
202
+ const sortOrder = desc ? "DESC" : "ASC";
203
+ const sortBy = id;
204
+
205
+ if (!(formState.tableForm.sortBy === sortBy && formState.tableForm.sortOrder === sortOrder)) {
206
+ dispatch({
207
+ action: "mutateTableForm",
208
+ data: {
209
+ ...formState.tableForm,
210
+ sortBy: id,
211
+ sortOrder: desc ? "DESC" : "ASC",
212
+ },
213
+ checkPathName,
214
+ });
215
+ }
216
+ }
217
+ };
218
+
219
+ const propsForInboxTable = useInboxTableConfig({
220
+ ...{
221
+ parentRoute,
222
+ onPageSizeChange,
223
+ formState,
224
+ totalCount: totalRecords,
225
+ table: filteredData,
226
+ dispatch,
227
+ onSortingByData,
228
+ tenantId,
229
+ checkPathName,
230
+ inboxStyles: { overflowX: "scroll", overflowY: "hidden" },
231
+ tableStyle: { width: "70%" },
232
+ },
233
+ });
234
+
235
+ const isInboxLoading = isLoading || isSearchLoading;
236
+
237
+ return (
238
+ <div className="app-container">
239
+ <InboxComposer
240
+ {...{
241
+ isInboxLoading,
242
+ PropsForInboxLinks,
243
+ ...propsForSearchForm,
244
+ ...propsForFilterForm,
245
+ // ...propsForMobileSortForm,
246
+ propsForInboxTable,
247
+ // propsForInboxMobileCards,
248
+ formState,
249
+ }}
250
+ />
251
+ </div>
252
+ );
145
253
  };
146
254
 
147
- export default Inbox;
255
+ export default Inbox;