@djb25/digit-ui-module-ekyc 1.0.13 → 1.0.15

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,334 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import { useHistory, useLocation } from "react-router-dom";
3
+ import {
4
+ FaChartLine,
5
+ FaUsers,
6
+ FaCheckCircle,
7
+ FaClock,
8
+ FaExclamationTriangle,
9
+ FaUserTie,
10
+ FaMapMarkedAlt,
11
+ FaSearch,
12
+ FaArrowRight,
13
+ } from "react-icons/fa";
14
+ import { ekycMockData } from "./mockData";
15
+ import ExecutiveLineChart from "./analytics/charts/ExecutiveLineChart";
16
+ import ExecutiveBarChart from "./analytics/charts/ExecutiveBarChart";
17
+ import ExecutivePieChart from "./analytics/charts/ExecutivePieChart";
18
+
19
+ const CeoDashboard = () => {
20
+ const history = useHistory();
21
+ const location = useLocation();
22
+ const [query, setQuery] = useState("");
23
+ const [statusFilter, setStatusFilter] = useState("All");
24
+
25
+ const basePath = location.pathname.replace(/\/ceo-dashboard$/, "");
26
+ const filters = ["All", "High Priority", "Stable", "Watchlist"];
27
+
28
+ const vendors = useMemo(() => ekycMockData.vendors || [], []);
29
+ const topKpis = useMemo(() => ekycMockData.topKpis || {}, []);
30
+ const dailyTrend = useMemo(() => ekycMockData.dailyTrend || [], []);
31
+ const selfVsAgency = useMemo(() => ekycMockData.selfVsAgency || [], []);
32
+ const pendingVsCompleted = useMemo(() => ekycMockData.pendingVsCompleted || [], []);
33
+ // const rejectionAnalysis = ekycMockData.rejectionAnalysis || [];
34
+
35
+ const filteredVendors = useMemo(() => {
36
+ return vendors
37
+ .filter((vendor) => {
38
+ if (statusFilter === "High Priority") return vendor.successRate < 70;
39
+ if (statusFilter === "Watchlist") return vendor.pending > 35000;
40
+ if (statusFilter === "Stable") return vendor.successRate >= 70;
41
+ return true;
42
+ })
43
+ .filter((vendor) => {
44
+ if (!query) return true;
45
+ const keyword = query.toLowerCase();
46
+ return (
47
+ vendor.name.toLowerCase().includes(keyword) ||
48
+ vendor.jurisdictions.some((zone) => zone.toLowerCase().includes(keyword)) ||
49
+ vendor.zones.some((zone) => zone.location?.toLowerCase().includes(keyword))
50
+ );
51
+ });
52
+ }, [vendors, query, statusFilter]);
53
+
54
+ const zoneRows = useMemo(() => {
55
+ return vendors
56
+ .flatMap((vendor) => vendor.zones.map((zone) => ({ ...zone, agency: vendor.name })))
57
+ .filter((row) => row.location)
58
+ .sort((a, b) => (b.activeDemand || 0) - (a.activeDemand || 0));
59
+ }, [vendors]);
60
+
61
+ const alerts = [
62
+ "North East District has 22% more pending accounts than last week",
63
+ "Agency 2 has a 12% improvement opportunity in Self eKYC adoption",
64
+ "Urgent: 18 supervisor escalations raised for NWS Bhera Enclave",
65
+ "Risk alert: Rejection trends are rising in Outer North clusters",
66
+ ];
67
+
68
+ const routeToVendor = (vendorId) => {
69
+ history.push(`${basePath}/vendors/${vendorId}`);
70
+ };
71
+
72
+ return (
73
+ <div className="ekyc-dashboard-wrapper">
74
+ <div className="dashboard-shell">
75
+ <section className="kpi-grid">
76
+ {[
77
+ {
78
+ title: "Total Water Connections",
79
+ value: topKpis.totalWaterConnections,
80
+ icon: <FaUsers />,
81
+ variant: "primary",
82
+ },
83
+ {
84
+ title: "Total eKYC Completed",
85
+ value: topKpis.totalEkycCompleted,
86
+ icon: <FaCheckCircle />,
87
+ variant: "success",
88
+ },
89
+ {
90
+ title: "Total Pending",
91
+ value: topKpis.totalPending,
92
+ icon: <FaClock />,
93
+ variant: "warning",
94
+ },
95
+ {
96
+ title: "Total Rejected",
97
+ value: topKpis.totalRejected,
98
+ icon: <FaExclamationTriangle />,
99
+ variant: "danger",
100
+ },
101
+ {
102
+ title: "Agency eKYC Completed",
103
+ value: topKpis.agencyEkycCompleted,
104
+ icon: <FaUserTie />,
105
+ variant: "info",
106
+ },
107
+ {
108
+ title: "Citizen Self eKYC Completed",
109
+ value: topKpis.citizenSelfEkycCompleted,
110
+ icon: <FaUsers />,
111
+ variant: "secondary",
112
+ },
113
+ {
114
+ title: "Today's eKYC",
115
+ value: topKpis.todaysEkyc,
116
+ icon: <FaChartLine />,
117
+ variant: "muted",
118
+ },
119
+ {
120
+ title: "Success Rate",
121
+ value: `${topKpis.successRate}%`,
122
+ icon: <FaCheckCircle />,
123
+ variant: "success",
124
+ },
125
+ {
126
+ title: "Rejection Percentage",
127
+ value: `${topKpis.rejectionPercentage}%`,
128
+ icon: <FaExclamationTriangle />,
129
+ variant: "danger",
130
+ },
131
+ ].map((card) => (
132
+ <article key={card.title} className={`kpi-card card-${card.variant}`}>
133
+ <div className="kpi-icon">{card.icon}</div>
134
+ <div>
135
+ <p className="kpi-title">{card.title}</p>
136
+ <h2>{card.value?.toLocaleString?.() ?? card.value}</h2>
137
+ </div>
138
+ </article>
139
+ ))}
140
+ </section>
141
+
142
+ <section className="vendor-section">
143
+ <div className="section-top">
144
+ <div>
145
+ <h2>Agency Performance Snapshot</h2>
146
+ <p>Search, filter, and navigate into detailed analytics for every assigned vendor.</p>
147
+ </div>
148
+ <div className="vendor-filters">
149
+ <div className="search-box">
150
+ <FaSearch />
151
+ <input type="text" placeholder="Search agency, jurisdiction, location" value={query} onChange={(e) => setQuery(e.target.value)} />
152
+ </div>
153
+ <div className="status-pill-group">
154
+ {filters.map((status) => (
155
+ <button key={status} className={`status-pill ${statusFilter === status ? "active" : ""}`} onClick={() => setStatusFilter(status)}>
156
+ {status}
157
+ </button>
158
+ ))}
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <div className="vendor-grid">
164
+ {filteredVendors.map((vendor) => (
165
+ <div key={vendor.id} className="vendor-card glass-card" onClick={() => routeToVendor(vendor.id)}>
166
+ <div className="vendor-card-top">
167
+ <div>
168
+ <p className="vendor-chip">{vendor.name}</p>
169
+ <h3>{vendor.assignedConnections.toLocaleString()}</h3>
170
+ <p className="vendor-subtitle">Assigned connections across {vendor.jurisdictions.length} jurisdiction(s)</p>
171
+ </div>
172
+ <div className="vendor-progress-value">{vendor.progress}%</div>
173
+ </div>
174
+ <div className="progress-bar">
175
+ <div className="progress-fill" style={{ width: `${vendor.progress}%` }} />
176
+ </div>
177
+ <div className="vendor-stats-grid">
178
+ <div>
179
+ <strong>{vendor.completedEkyc.toLocaleString()}</strong>
180
+ <span>Agency eKYC</span>
181
+ </div>
182
+ <div>
183
+ <strong>{vendor.selfEkyc.toLocaleString()}</strong>
184
+ <span>Self eKYC</span>
185
+ </div>
186
+ <div>
187
+ <strong>{vendor.pending.toLocaleString()}</strong>
188
+ <span>Pending</span>
189
+ </div>
190
+ <div>
191
+ <strong>{vendor.rejected.toLocaleString()}</strong>
192
+ <span>Rejected</span>
193
+ </div>
194
+ </div>
195
+ <div className="vendor-card-footer">
196
+ <span>{vendor.activeSurveyors} Surveyors</span>
197
+ <span>{vendor.supervisors} Supervisors</span>
198
+ <span>{vendor.jurisdictions.join(", ")}</span>
199
+ </div>
200
+ <div className="vendor-card-action">
201
+ <span>Explore analytics</span>
202
+ <FaArrowRight />
203
+ </div>
204
+ </div>
205
+ ))}
206
+ </div>
207
+ </section>
208
+
209
+ <section className="analytics-grid">
210
+ <div className="analytics-panel glass-card">
211
+ <div className="section-header">
212
+ <h2>Daily eKYC Trend</h2>
213
+ <span className="badge success">Live index</span>
214
+ </div>
215
+ <ExecutiveLineChart data={dailyTrend} dataKey="completed" name="Completed" />
216
+ </div>
217
+
218
+ <div className="analytics-panel glass-card">
219
+ <div className="section-header">
220
+ <h2>Self vs Agency eKYC</h2>
221
+ <span className="badge info">Adoption</span>
222
+ </div>
223
+ <ExecutiveBarChart
224
+ data={selfVsAgency}
225
+ categories={[
226
+ { key: "agency", name: "Agency" },
227
+ { key: "citizen", name: "Citizen" },
228
+ ]}
229
+ />
230
+ </div>
231
+
232
+ <div className="analytics-panel glass-card">
233
+ <div className="section-header">
234
+ <h2>Pending vs Completed</h2>
235
+ <span className="badge warning">Operational pulse</span>
236
+ </div>
237
+ <ExecutivePieChart data={pendingVsCompleted} dataKey="completed" labelKey="label" />
238
+ </div>
239
+ </section>
240
+
241
+ <section className="zone-section">
242
+ <div className="section-header">
243
+ <div>
244
+ <h2>
245
+ <FaMapMarkedAlt /> Jurisdiction Performance
246
+ </h2>
247
+ <p>Top zones evaluated from the full agency dataset.</p>
248
+ </div>
249
+ <button className="secondary-button">Export Zone Data</button>
250
+ </div>
251
+
252
+ <div className="table-wrapper glass-card">
253
+ <table>
254
+ <thead>
255
+ <tr>
256
+ <th>Agency</th>
257
+ <th>Location</th>
258
+ <th>District</th>
259
+ <th>Assigned</th>
260
+ <th>Agency eKYC</th>
261
+ <th>Pending</th>
262
+ <th>Progress</th>
263
+ </tr>
264
+ </thead>
265
+ <tbody>
266
+ {zoneRows.slice(0, 10).map((zone, index) => (
267
+ <tr key={`${zone.agency}-${zone.location}-${index}`}>
268
+ <td>{zone.agency}</td>
269
+ <td>{zone.location}</td>
270
+ <td>{zone.district || zone.cluster}</td>
271
+ <td>{zone.activeDemand?.toLocaleString() ?? "—"}</td>
272
+ <td>{zone.pppZones?.toLocaleString() ?? "—"}</td>
273
+ <td>{zone.inactiveDemand?.toLocaleString() ?? "—"}</td>
274
+ <td>
275
+ <span className="status-badge success">
276
+ {Math.min(100, Math.round(((zone.pppZones || 0) / Math.max(zone.activeDemand || 1, 1)) * 100))}%
277
+ </span>
278
+ </td>
279
+ </tr>
280
+ ))}
281
+ </tbody>
282
+ </table>
283
+ </div>
284
+ </section>
285
+
286
+ <section className="bottom-grid">
287
+ <div className="alerts-panel glass-card">
288
+ <div className="section-header">
289
+ <h2>
290
+ <FaExclamationTriangle /> Critical Alerts
291
+ </h2>
292
+ </div>
293
+ <div className="alerts-list">
294
+ {alerts.map((alert) => (
295
+ <div key={alert} className="alert-card">
296
+ <FaExclamationTriangle />
297
+ <p>{alert}</p>
298
+ </div>
299
+ ))}
300
+ </div>
301
+ </div>
302
+
303
+ <div className="efficiency-panel glass-card">
304
+ <div className="section-header">
305
+ <h2>
306
+ <FaUserTie /> Workforce & Efficiency
307
+ </h2>
308
+ </div>
309
+ <div className="efficiency-grid">
310
+ <div className="efficiency-box">
311
+ <h3>48</h3>
312
+ <p>Supervisors Online</p>
313
+ </div>
314
+ <div className="efficiency-box">
315
+ <h3>168</h3>
316
+ <p>Surveyors Active</p>
317
+ </div>
318
+ <div className="efficiency-box">
319
+ <h3>{topKpis.todayEkyc?.toLocaleString()}</h3>
320
+ <p>eKYC Completed Today</p>
321
+ </div>
322
+ <div className="efficiency-box">
323
+ <h3>{topKpis.successRate}%</h3>
324
+ <p>Operational Efficiency</p>
325
+ </div>
326
+ </div>
327
+ </div>
328
+ </section>
329
+ </div>
330
+ </div>
331
+ );
332
+ };
333
+
334
+ export default CeoDashboard;
@@ -76,6 +76,14 @@ const DesktopInbox = ({ tableConfig, filterComponent, ...props }) => {
76
76
  return <span className={`ekyc-status-tag ${status}`}>{t(`${status}`)}</span>;
77
77
  },
78
78
  },
79
+ {
80
+ Header: t("EKYC_EKYC_STATUS"),
81
+ accessor: "ekycStatus",
82
+ Cell: ({ row }) => {
83
+ const ekycStatus = row.original?.ekycstatus || "NA";
84
+ return <span className={`ekyc-status-tag ${ekycStatus}`}>{t(`${ekycStatus}`)}</span>
85
+ }
86
+ },
79
87
  {
80
88
  Header: t("EKYC_ACTION"),
81
89
  accessor: "status",
@@ -34,11 +34,17 @@ const EKYCCard = () => {
34
34
  // },
35
35
  {
36
36
  label: t("CEO_M.F_DOR_FINANCE_VIEW"),
37
- link: `/digit-ui/employee/ekyc/ceo-dashboard`
37
+ link: `/digit-ui/employee/ekyc/ceo-dashboard`,
38
38
  },
39
+
40
+ // {
41
+ // label: t("EKYC_MAPPING"),
42
+ // link: `/digit-ui/employee/ekyc/mapping`,
43
+ // },
44
+
39
45
  {
40
- label: t("EKYC_MAPPING"),
41
- link: `/digit-ui/employee/ekyc/mapping`,
46
+ label: t("EKYC_ASSIGN"),
47
+ link: `/digit-ui/employee/ekyc/assign`,
42
48
  },
43
49
  ],
44
50
  };