diff --git a/health/micro-ui/web/micro-ui-internals/example/package.json b/health/micro-ui/web/micro-ui-internals/example/package.json
index 71f104204fa..5f524e73f46 100644
--- a/health/micro-ui/web/micro-ui-internals/example/package.json
+++ b/health/micro-ui/web/micro-ui-internals/example/package.json
@@ -16,7 +16,7 @@
"@egovernments/digit-ui-module-utilities": "1.0.3",
"@egovernments/digit-ui-react-components": "1.8.12",
"@egovernments/digit-ui-module-hcmworkbench": "0.1.0",
- "@egovernments/digit-ui-module-campaign-manager": "0.3.0",
+ "@egovernments/digit-ui-module-campaign-manager": "0.4.0",
"@egovernments/digit-ui-module-microplan": "0.0.1",
"@egovernments/digit-ui-module-health-payments": "0.0.1",
"http-proxy-middleware": "^1.0.5",
diff --git a/health/micro-ui/web/micro-ui-internals/example/src/setupProxy.js b/health/micro-ui/web/micro-ui-internals/example/src/setupProxy.js
index 6f35778c42a..ef0a76bdc24 100644
--- a/health/micro-ui/web/micro-ui-internals/example/src/setupProxy.js
+++ b/health/micro-ui/web/micro-ui-internals/example/src/setupProxy.js
@@ -97,7 +97,9 @@ module.exports = function (app) {
"/plan-service",
"/health-project",
"/service-request",
- "/census-service"
+ "/census-service",
+ "/health-attendance",
+ "/health-individual"
].forEach((location) => app.use(location, createProxy));
["/pb-egov-assets"].forEach((location) => app.use(location, assetsProxy));
["/mdms-v2/v2/_create"].forEach((location) => app.use(location, mdmsProxy));
diff --git a/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/campaign.scss b/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/campaign.scss
index 8a5dc65c2a0..d01ad1d128b 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/campaign.scss
+++ b/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/campaign.scss
@@ -236,4 +236,55 @@
> div:nth-of-type(1) {
width: 69%;
}
-}
\ No newline at end of file
+}
+
+.attendanceCell {
+ display: flex;
+}
+
+.chip-container {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem; /* Adjust the spacing between chips and the button */
+}
+
+.chip-wrapper {
+ display: inline-flex;
+}
+
+.more-button {
+ margin-left: 0.5rem; /* Adjust the spacing between chips and the + button */
+}
+
+/* Container for the progress bar */
+.progress-bar-container {
+ position: relative;
+ width: 20rem;
+ max-width: 20rem;
+ height: 20px;
+ background-color: #f3f3f3;
+ border-radius: 10px;
+ overflow: hidden;
+ border: 1px solid #d6d5d4;
+}
+
+/* The fill of the progress bar */
+.progress-bar-fill {
+ height: 100%;
+ background-color: #2BD27F; /* Use your PRIMARY_COLOR */
+ transition: width 0.4s ease-in-out;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* Text inside the progress bar */
+.progress-bar-text {
+ position: absolute;
+ width: 100%;
+ text-align: center;
+ font-size: 14px;
+ color: #fff;
+ font-weight: bold;
+ pointer-events: none;
+}
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/package.json b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/package.json
index 9c9faa70b01..36ec79ba0ce 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/package.json
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@egovernments/digit-ui-module-campaign-manager",
- "version": "0.3.0",
+ "version": "0.4.0",
"description": "Campaign",
"main": "dist/index.js",
"module": "dist/index.modern.js",
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/Module.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/Module.js
index aa986b565db..af8099e913c 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/Module.js
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/Module.js
@@ -57,6 +57,9 @@ import MapView from "./components/MapView";
import NoResultsFound from "./components/NoResultsFound";
import UploadDataMappingWrapper from "./components/UploadDataMappingWrapper";
import DataUploadWrapper from "./components/DataUploadWrapper";
+import Attendance from "./pages/employee/Attendance";
+import IndividualUserTable from "./components/IndividualUserTable";
+import ProgressBar from "./components/ProgressBar";
/**
* MDMS Module name
@@ -175,6 +178,9 @@ const componentsToRegister = {
NoResultsFound,
UploadDataMappingWrapper,
DataUploadWrapper,
+ Attendance,
+ IndividualUserTable,
+ ProgressBar
};
const overrideHooks = () => {
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/IndividualUserTable.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/IndividualUserTable.js
new file mode 100644
index 00000000000..b43d684c1d7
--- /dev/null
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/IndividualUserTable.js
@@ -0,0 +1,257 @@
+import React, { useState, useCallback, useEffect } from 'react';
+import { Card, Button, PopUp, Loader, Chip, TextInput } from "@egovernments/digit-ui-components";
+import DataTable from "react-data-table-component";
+import { CustomSVG } from "@egovernments/digit-ui-components";
+import { tableCustomStyle } from "./tableCustomStyle";
+import { useTranslation } from "react-i18next";
+import ProgressBar from './ProgressBar';
+
+const SupervisorPopup = ({ setShowPopUp, supervisors }) => {
+ const { t } = useTranslation();
+ return (
+ setShowPopUp(false)}
+ onClose={() => setShowPopUp(false)}
+ >
+
+ {supervisors.map((supervisor, index) => (
+
+ ))}
+
+
+ );
+};
+
+const IndividualUserTable = ({ tenantId, staffAttendeeIds = [], supervisorName = "" }) => {
+ const { t } = useTranslation();
+ const [totalRows, setTotalRows] = useState(0);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [paginatedData, setPaginatedData] = useState([]);
+ const [chipPopUpRowId, setChipPopUpRowId] = useState(null);
+
+ const individualUsers = {
+ url: `/health-individual/v1/_search`,
+ params: {
+ tenantId: tenantId,
+ limit: 1000,
+ offset: 0
+ },
+ body: {
+ Individual: {},
+ },
+ };
+
+ const { isLoading, data, isFetching } = Digit.Hooks.useCustomAPIHook(individualUsers);
+
+ const getDisplayData = useCallback(() => {
+ if (!data?.Individual) return [];
+
+ const filteredData = data.Individual.filter(user => {
+ const userName = user.name?.givenName?.toLowerCase() || '';
+ return userName.includes(searchQuery.toLowerCase());
+ }).map(user => ({
+ ...user,
+ isAssigned: staffAttendeeIds.includes(user.id)
+ }));
+
+ setTotalRows(filteredData.length);
+
+ const startIndex = (currentPage - 1) * rowsPerPage;
+ const endIndex = startIndex + rowsPerPage;
+ return filteredData.slice(startIndex, endIndex);
+ }, [data, searchQuery, staffAttendeeIds, currentPage, rowsPerPage]);
+
+ useEffect(() => {
+ if (data?.Individual) {
+ setPaginatedData(getDisplayData());
+ }
+ }, [data, searchQuery, currentPage, rowsPerPage, getDisplayData]);
+
+ const handlePaginationChange = (page) => {
+ setCurrentPage(page);
+ };
+
+ const handleRowsPerPageChange = (newPerPage) => {
+ setRowsPerPage(newPerPage);
+ setCurrentPage(1);
+ };
+
+ const handleSearchChange = (e) => {
+ setSearchQuery(e.target.value);
+ setCurrentPage(1);
+ };
+
+ const handleAssign = (userId) => {
+ //code to be written
+ };
+
+ const handleUnassign = (userId) => {
+ //code to be written
+ };
+
+ const columns = [
+ {
+ name: t('Name'),
+ selector: row => row.name?.givenName || '',
+ sortable: true,
+ },
+ {
+ name: t('Mobile Number'),
+ selector: row => row.mobileNumber || '',
+ sortable: true,
+ },
+ {
+ name: t("USER_TYPE"),
+ selector: row => row?.userDetails?.type || '',
+ sortable: true
+ },
+ {
+ name: t('Assigned Supervisor'),
+ selector: row => row.isAssigned ? supervisorName : t('Not Assigned'),
+ sortable: true,
+ grow: 2,
+ cell: row => {
+ const supervisors = row.isAssigned ? [supervisorName] : [];
+
+ return (
+
+ {supervisors.length > 0 ? (
+
+ {supervisors.slice(0, 2).map((supervisor, index) => (
+
+
+
+ ))}
+
+ {supervisors.length > 2 && (
+
+ ) : (
+
{t('Not Assigned')}
+ )}
+
+ );
+ },
+ },
+ {
+ name: t('Actions'),
+ cell: row => (
+
+ {row.isAssigned ? (
+
+ )
+ },
+ ];
+
+ return (
+
+ {isLoading &&
}
+ {!isLoading && (
+
+
+
+
+
+
}
+ pagination
+ paginationServer
+ customStyles={tableCustomStyle}
+ paginationTotalRows={totalRows}
+ onChangePage={handlePaginationChange}
+ onChangeRowsPerPage={handleRowsPerPageChange}
+ paginationPerPage={rowsPerPage}
+ sortIcon={
}
+ paginationRowsPerPageOptions={[5, 10, 15, 20]}
+ noDataComponent={
{t('No records found')}
}
+ />
+
+ {/* Progress Bar Section */}
+
+
+
{t("REGISTER_ASSIGNED_USERS")}
+
+
{`${40}%`}
+
+
+
+ )}
+
+ );
+
+};
+
+export default IndividualUserTable;
\ No newline at end of file
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/ProgressBar.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/ProgressBar.js
new file mode 100644
index 00000000000..a86735f9016
--- /dev/null
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/ProgressBar.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+const ProgressBar = ({ amount, total }) => {
+ const percentage = Math.min((amount / total) * 100, 100);
+
+ return (
+
+
+ {`${percentage.toFixed(1)}%`}
+
+
+ );
+};
+
+export default ProgressBar;
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/tableCustomStyle.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/tableCustomStyle.js
index 380ef998802..d615f538d1c 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/tableCustomStyle.js
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/tableCustomStyle.js
@@ -1,28 +1,15 @@
export const tableCustomStyle = {
tableWrapper: {
- style: {
- overflow: "visible",
-
- // overflow: "scroll",
- },
+ style: {},
},
table: {
- style: {
- overflow: "visible",
- // overflow: "scroll",
- },
+ style: {},
},
responsiveWrapper: {
- style: {
- overflow: "visible",
- // overflow: "scroll",
- },
+ style: {},
},
contextMenu: {
- style: {
- overflow: "visible",
- // overflow: "scroll",
- },
+ style: {},
},
header: {
style: {
@@ -35,33 +22,17 @@ export const tableCustomStyle = {
"&:hover": {
backgroundColor: "#FBEEE8",
},
+ borderBottom: "1px solid #D6D5D4", // Horizontal lines between rows
},
},
headRow: {
style: {
- borderTopStyle: "solid",
- borderTopWidth: "1px",
- borderTopColor: "#D6D5D4",
+ borderBottom: "1px solid #D6D5D4",
backgroundColor: "#EEEEEE",
},
},
headCells: {
style: {
- "&:first-of-type": {
- borderLeftStyle: "solid",
- borderLeftWidth: "1px",
- borderLeftColor: "#D6D5D4",
- borderTopLeftRadius: "0.25rem",
- },
- "&:last-of-type": {
- borderLeftStyle: "solid",
- borderLeftWidth: "1px",
- borderLeftColor: "#D6D5D4",
- borderTopRightRadius: "0.25rem",
- },
- borderRightStyle: "solid",
- borderRightWidth: "1px",
- borderRightColor: "#D6D5D4",
fontFamily: "Roboto",
fontWeight: "700",
fontStyle: "normal",
@@ -69,19 +40,12 @@ export const tableCustomStyle = {
color: "#0B4B66",
padding: "16px",
lineHeight: "1.14rem",
- zIndex: 10,
+ borderBottom: "1px solid #D6D5D4", // Only horizontal lines in header
+ borderRight: "none", // Remove vertical lines
},
},
cells: {
style: {
- "&:first-of-type": {
- borderLeftStyle: "solid",
- borderLeftWidth: "1px",
- borderLeftColor: "#D6D5D4",
- },
- borderRightStyle: "solid",
- borderRightWidth: "1px",
- borderRightColor: "#D6D5D4",
color: "#363636",
fontFamily: "Roboto",
fontStyle: "normal",
@@ -90,6 +54,8 @@ export const tableCustomStyle = {
textAlign: "left",
fontSize: "16px",
padding: "16px",
+ borderBottom: "1px solid #D6D5D4", // Horizontal lines between rows
+ borderRight: "none", // Remove vertical lines
},
pagination: {
style: {
@@ -103,6 +69,7 @@ export const tableCustomStyle = {
},
};
+
export const getTableCustomStyle = (freezeFirstColumn = false) => ({
tableWrapper: {
style: {
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/configs/UICustomizations.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/configs/UICustomizations.js
index f4a1e430249..d40e7e01797 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/configs/UICustomizations.js
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/configs/UICustomizations.js
@@ -464,6 +464,19 @@ export const UICustomizations = {
const navEvent1 = new PopStateEvent("popstate");
window.dispatchEvent(navEvent1);
break;
+ case "ACTION_LABEL_CONFIGURE_REGISTER":
+ window.history.pushState(
+ {
+ name: row?.campaignName,
+ data: row,
+ projectId: row?.projectId,
+ },
+ "",
+ `/${window.contextPath}/employee/campaign/attendance`
+ );
+ const navEvent2 = new PopStateEvent("popstate");
+ window.dispatchEvent(navEvent2);
+ break;
case "ACTION_LABEL_UPDATE_BOUNDARY_DETAILS":
window.history.pushState(
@@ -509,6 +522,7 @@ export const UICustomizations = {
...(row?.status === "created" ? [{ key: 1, code: "ACTION_LABEL_UPDATE_DATES", i18nKey: t("ACTION_LABEL_UPDATE_DATES") }] : []),
{ key: 2, code: "ACTION_LABEL_CONFIGURE_APP", i18nKey: t("ACTION_LABEL_CONFIGURE_APP") },
{ key: 3, code: "ACTION_LABEL_VIEW_TIMELINE", i18nKey: t("ACTION_LABEL_VIEW_TIMELINE") },
+ { key: 4, code: "ACTION_LABEL_CONFIGURE_REGISTER", i18nKey: t("ACTION_LABEL_CONFIGURE_REGISTER") },
...(row?.status === "created"
? [{ key: 1, code: "ACTION_LABEL_UPDATE_BOUNDARY_DETAILS", i18nKey: t("ACTION_LABEL_UPDATE_BOUNDARY_DETAILS") }]
: []),
@@ -799,6 +813,20 @@ export const UICustomizations = {
window.dispatchEvent(navEvent1);
break;
+ case "ACTION_LABEL_CONFIGURE_REGISTER":
+ window.history.pushState(
+ {
+ name: row?.campaignName,
+ data: row,
+ projectId: row?.projectId,
+ },
+ "",
+ `/${window.contextPath}/employee/campaign/attendance`
+ );
+ const navEvent2 = new PopStateEvent("popstate");
+ window.dispatchEvent(navEvent2);
+ break;
+
default:
console.log(value);
break;
@@ -831,6 +859,7 @@ export const UICustomizations = {
...(row?.status === "created" ? [{ key: 1, code: "ACTION_LABEL_UPDATE_DATES", i18nKey: t("ACTION_LABEL_UPDATE_DATES") }] : []),
{ key: 2, code: "ACTION_LABEL_CONFIGURE_APP", i18nKey: t("ACTION_LABEL_CONFIGURE_APP") },
{ key: 3, code: "ACTION_LABEL_VIEW_TIMELINE", i18nKey: t("ACTION_LABEL_VIEW_TIMELINE") },
+ { key: 4, code: "ACTION_LABEL_CONFIGURE_REGISTER", i18nKey: t("ACTION_LABEL_CONFIGURE_REGISTER") },
...(row?.status === "created"
? [{ key: 1, code: "ACTION_LABEL_UPDATE_BOUNDARY_DETAILS", i18nKey: t("ACTION_LABEL_UPDATE_BOUNDARY_DETAILS") }]
: []),
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/index.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/index.js
index da35cdc455a..bae573fa6e7 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/index.js
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/index.js
@@ -19,6 +19,7 @@ import useMDMSServiceSearch from "./useMDMSServiceSearch";
import useBoundaryHome from "./useBoundaryHome";
import useFetchFromMicroplan from "./useFetchFromMicroplan";
import { useReadExcelData, useUpdateAndUploadExcel } from "./useReadExcelData";
+import useAttendanceWithUserDetails from "./useAttendanceWithUserDetails";
const UserService = {};
@@ -48,6 +49,7 @@ const campaign = {
useFetchFromMicroplan,
useReadExcelData,
useUpdateAndUploadExcel,
+ useAttendanceWithUserDetails
};
const Hooks = {
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/services/searchAttendanceWithUserDetails.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/services/searchAttendanceWithUserDetails.js
new file mode 100644
index 00000000000..bdaf801a629
--- /dev/null
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/services/searchAttendanceWithUserDetails.js
@@ -0,0 +1,161 @@
+const searchAttendanceWithUserDetails = async ({ tenantId, limit, offset }) => {
+ try {
+
+ // First API call to get attendance register
+ const registerResponse = await Digit.CustomService.getResponse({
+ url: "/health-attendance/v1/_search",
+ method: "POST",
+ params: { tenantId, limit, offset },
+ body: {
+ attendanceRegister: {
+ tenantId,
+ },
+ },
+ });
+
+ if (!registerResponse?.attendanceRegister?.length) {
+ return {
+ registers: [],
+ staffAttendeeMap: {},
+ attendeeDetailsMap: {},
+ userDetails: [],
+ isError: false
+ };
+ }
+
+ // Create maps and collect IDs
+ const staffAttendeeMap = {};
+ const individualIds = new Set();
+ const staffUserIds = new Set();
+
+ // Populate maps and collect IDs
+ registerResponse.attendanceRegister.forEach((register) => {
+ const staffMember = register.staff?.[0];
+ if (staffMember?.userId) {
+ // Collect staff user IDs
+ staffUserIds.add(staffMember.userId);
+
+ // Map staff ID to their attendees
+ staffAttendeeMap[staffMember.userId] = register.attendees || [];
+
+ // Collect all individual IDs from attendees
+ register.attendees?.forEach(attendee => {
+ if (attendee.individualId) {
+ individualIds.add(attendee.individualId);
+ }
+ });
+ }
+ });
+
+ // If no IDs found, return early
+ if (individualIds.size === 0 && staffUserIds.size === 0) {
+ return {
+ registers: registerResponse.attendanceRegister,
+ staffAttendeeMap,
+ attendeeDetailsMap: {},
+ userDetails: [],
+ isError: false
+ };
+ }
+
+ // Get staff user details
+ const staffResponse = await Digit.CustomService.getResponse({
+ url: "/health-individual/v1/_search",
+ method: "POST",
+ params: {
+ limit: 1000,
+ offset: 0,
+ tenantId,
+ },
+ body: {
+ Individual: {
+ id: Array.from(staffUserIds)
+ },
+ },
+ });
+
+
+ // Get attendee details
+ const individualResponse = await Digit.CustomService.getResponse({
+ url: "/health-individual/v1/_search",
+ method: "POST",
+ params: {
+ limit: 1000,
+ offset: 0,
+ tenantId,
+ },
+ body: {
+ Individual: {
+ id: Array.from(individualIds)
+ },
+ },
+ });
+
+ // Create map of staff details
+ const userDetails = staffResponse?.Individual.map(user => {
+ // Find the register that contains this staff member
+ const registerWithStaff = registerResponse.attendanceRegister.find(
+ register => register.staff?.[0]?.userId === user.id
+ );
+
+ return {
+ id: user.id,
+ name: user?.name?.givenName,
+ dob: user.dateOfBirth,
+ email: user?.email,
+ mobileNumber: user?.mobileNumber,
+ registerId: registerWithStaff?.staff?.[0]?.registerId || null // Add registerId here
+ };
+ });
+
+ // Create map of individual details
+ const attendeeDetailsMap = {};
+ individualResponse?.Individual.forEach(individual => {
+ attendeeDetailsMap[individual.id] = {
+ name: individual?.name?.givenName,
+ dob: individual.dateOfBirth,
+ email: individual?.email,
+ mobileNumber: individual?.mobileNumber,
+ };
+ });
+
+ // Create a map for quick lookup of staff details
+ const staffDetailsMap = {};
+ userDetails.forEach(user => {
+ staffDetailsMap[user.id] = user;
+ });
+
+ // Enhance staffAttendeeMap with both staff and individual details
+ Object.keys(staffAttendeeMap).forEach(staffId => {
+ const enhancedStaffData = {
+ staffDetails: staffDetailsMap[staffId] || null,
+ attendees: staffAttendeeMap[staffId].map(attendee => ({
+ ...attendee,
+ individualDetails: attendeeDetailsMap[attendee.individualId] || null
+ }))
+ };
+ staffAttendeeMap[staffId] = enhancedStaffData;
+ });
+
+ return {
+ registers: registerResponse.attendanceRegister,
+ staffAttendeeMap,
+ attendeeDetailsMap,
+ userDetails,
+ isError: false,
+ };
+
+ } catch (error) {
+ console.error("Error in searchAttendanceWithUserDetails:", error);
+ return {
+ registers: [],
+ staffAttendeeMap: {},
+ attendeeDetailsMap: {},
+ userDetails: [],
+ isError: true,
+ error: error.message || "An unknown error occurred",
+ };
+ }
+ };
+
+ export default searchAttendanceWithUserDetails;
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/useAttendanceWithUserDetails.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/useAttendanceWithUserDetails.js
new file mode 100644
index 00000000000..3ffb310682e
--- /dev/null
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/hooks/useAttendanceWithUserDetails.js
@@ -0,0 +1,17 @@
+// hooks/useAttendanceWithUserDetails.js
+import { useQuery } from "react-query";
+import searchAttendanceWithUserDetails from "../hooks/services/searchAttendanceWithUserDetails";
+
+const useAttendanceWithUserDetails = ({ tenantId, limit, offset, config = {} }) => {
+ console.log("hook eorking");
+ return useQuery(
+ ["ATTENDANCE_WITH_USER_DETAILS", tenantId, limit, offset, config?.queryKey],
+ () => searchAttendanceWithUserDetails({ tenantId, limit, offset }),
+ {
+ enabled: true,
+ ...config,
+ }
+ );
+};
+
+export default useAttendanceWithUserDetails;
\ No newline at end of file
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/Attendance.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/Attendance.js
new file mode 100644
index 00000000000..800f6314351
--- /dev/null
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/Attendance.js
@@ -0,0 +1,339 @@
+import { Card, Button, PopUp, Stepper, Loader, Chip, TextInput } from "@egovernments/digit-ui-components";
+import React, { Fragment, useState, useEffect, useCallback } from "react";
+import { useTranslation } from "react-i18next";
+import { useHistory } from "react-router-dom";
+import DataTable from "react-data-table-component";
+import { CustomSVG } from "@egovernments/digit-ui-components";
+import useAttendanceWithUserDetails from "../../hooks/useAttendanceWithUserDetails";
+import IndividualUserTable from "../../components/IndividualUserTable";
+import { tableCustomStyle } from "../../components/tableCustomStyle";
+import ProgressBar from "../../components/ProgressBar";
+
+const Wrapper = ({ setShowPopUp, alreadyQueuedSelectedState }) => {
+ const { t } = useTranslation();
+ return (
+ setShowPopUp(false)}
+ onClose={() => setShowPopUp(false)}
+ >
+
+ {alreadyQueuedSelectedState?.map((item, index) => (
+
+ ))}
+
+
+ );
+};
+
+const Attendance = () => {
+ const { t } = useTranslation();
+ const history = useHistory();
+ const tenantId = Digit.ULBService.getCurrentTenantId();
+ const [showToast, setShowToast] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [totalRows, setTotalRows] = useState(0);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [paginatedData, setPaginatedData] = useState([]);
+ const [filteredData, setFilteredData] = useState([]);
+ const [assignUsersPopup, setAssignUsersPopup] = useState(false);
+ const [curAttendeesList, setCurAttendeesList] = useState([]);
+ const [curSupervisor, setCurSupervisor] = useState("");
+ const [curRegisterId, setCurRegisterId] = useState("");
+ const [chipPopUp, setChipPopUp] = useState(null);
+ const [chipPopUpRowId, setChipPopUpRowId] = useState(null);
+ const [currentStep, setCurrentStep] = useState(3);
+ const [active, setActive] = useState(3);
+
+ const { isLoading, data: userDetails, refetch: refetchAttendance } = useAttendanceWithUserDetails({
+ tenantId,
+ limit: 100,
+ offset: 0,
+ config: { enabled: true }
+ });
+
+ // Filter and paginate data
+ useEffect(() => {
+ if (userDetails?.userDetails) {
+ const filtered = searchQuery.trim() === ''
+ ? userDetails.userDetails
+ : userDetails.userDetails.filter(user =>
+ user.name?.toLowerCase()?.includes(searchQuery.toLowerCase())
+ );
+
+ setFilteredData(filtered);
+ setTotalRows(filtered.length);
+ setCurrentPage(1);
+ }
+ }, [userDetails, searchQuery]);
+
+ // Handle pagination
+ useEffect(() => {
+ if (filteredData.length > 0) {
+ const startIndex = (currentPage - 1) * rowsPerPage;
+ const endIndex = startIndex + rowsPerPage;
+ setPaginatedData(filteredData.slice(startIndex, endIndex));
+ }
+ }, [filteredData, currentPage, rowsPerPage]);
+
+ const handlePaginationChange = (page) => {
+ setCurrentPage(page);
+ };
+
+ const handleRowsPerPageChange = (newPerPage) => {
+ setRowsPerPage(newPerPage);
+ setCurrentPage(1);
+ };
+
+ const handleSearchChange = (e) => {
+ setSearchQuery(e.target.value);
+ };
+
+ const columns = [
+ {
+ name: t("NAME"),
+ selector: (row) => {
+ return (
+
+ {row.name || t("NA")}
+
+ );
+ },
+ sortable: true,
+ sortFunction: (rowA, rowB) => {
+ const nameA = t(rowA.name).toLowerCase();
+ const nameB = t(rowB.name).toLowerCase();
+ if (nameA < nameB) return -1;
+ if (nameA > nameB) return 1;
+ return 0;
+ },
+ },
+ {
+ name: t("EMAIL"),
+ selector: (row) => {
+ return (
+
+ {row.email || t("NA")}
+
+ );
+ },
+ sortable: false,
+ },
+ {
+ name: t("CONTACT_NUMBER"),
+ selector: (row) => {
+ return (
+
+ {row?.mobileNumber || t("NA")}
+
+ );
+ },
+ sortable: true,
+ sortFunction: (rowA, rowB) => {
+ const numberA = parseInt(rowA.number, 10);
+ const numberB = parseInt(rowB.number, 10);
+ if (isNaN(numberA)) return 1; // Treat invalid numbers as larger
+ if (isNaN(numberB)) return -1;
+
+ if (numberA < numberB) return -1;
+ if (numberA > numberB) return 1;
+ return 0;
+ },
+ },
+ {
+ name: t("USERS_ASSIGNED"),
+ sortable: false,
+ grow: 2,
+ cell: (row) => {
+ return (
+
+ {userDetails?.staffAttendeeMap[row?.id]?.attendees.length > 0 && (
+
+
+ {userDetails?.staffAttendeeMap[row?.id]?.attendees.slice(0, 2).map((item, index) => (
+
+
+
+ ))}
+ {userDetails?.staffAttendeeMap[row?.id]?.attendees.length > 2 && (
+
setChipPopUpRowId(row.id)}
+ variation="link"
+ className="more-button"
+ style={{
+ height: "2rem",
+ minWidth: "4.188rem",
+ minHeight: "2rem",
+ padding: "0.5rem",
+ justifyContent: "center",
+ alignItems: "center",
+ }}
+ textStyles={{
+ height: "auto",
+ fontSize: "0.875rem",
+ fontWeight: "400",
+ width: "100%",
+ lineHeight: "16px",
+ color: "#C84C0E",
+ }}
+ />
+ )}
+
+ {chipPopUpRowId === row.id && (
+
+ )}
+
+ )}
+ {userDetails?.staffAttendeeMap[row?.id]?.attendees.length === 0 && (
+
+ {t("USERS_NOT_ASSIGNED")}
+
+ )}
+
+
+ );
+ },
+ },
+ {
+ name: t("ACTION"),
+ sortable: false,
+ cell: (row) => {
+ return (
+ {setAssignUsersPopup(row)
+ setCurRegisterId(row?.registerId)
+ setCurSupervisor(row?.name)
+ const attendeesList = userDetails?.staffAttendeeMap?.[row?.id]?.attendees;
+ setCurAttendeesList(Array.isArray(attendeesList) ? attendeesList.map(attendee => attendee.individualId) : []);
+ }}
+ />
+ );
+ },
+ },
+ ];
+
+ return (
+ <>
+ {isLoading && }
+ {!isLoading && (
+
+ )}
+ {!isLoading && (
+
+
+
+ {t("SUPERVISORS_AND_TEAM_MEMBERS")}
+
+
+
+
+
{t("REGISTER_ASSIGNED_USERS")}
+
+
{`${40}%`}
+
+
+
+
+
+
+ {t("SUPERVISOR_BODY_TEXT")}
+
+
+ )}
+
+ {!isLoading && (
+
+
+
+
+
+ }
+ pagination
+ paginationServer
+ customStyles={tableCustomStyle}
+ paginationTotalRows={totalRows}
+ onChangePage={handlePaginationChange}
+ onChangeRowsPerPage={handleRowsPerPageChange}
+ paginationPerPage={rowsPerPage}
+ sortIcon={}
+ paginationRowsPerPageOptions={[5, 10, 15, 20]}
+ noDataComponent={{t("NO_RECORDS_FOUND")}
}
+ />
+
+ {assignUsersPopup && (
+
+ ]}
+ onOverlayClick={() => setAssignUsersPopup(false)}
+ footerChildren={[
+ setAssignUsersPopup(false)}
+ style={{ minWidth: "200px" }}
+ />
+ ]}
+ sortFooterChildren={true}
+ onClose={() => setAssignUsersPopup(false)}
+ />
+ )}
+
+ )}
+ >
+ );
+};
+
+export default Attendance;
\ No newline at end of file
diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/index.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/index.js
index 122578edc9c..83ee1ff2aee 100644
--- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/index.js
+++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/index.js
@@ -16,6 +16,7 @@ import UpdateChecklist from "./UpdateChecklist";
import BoundaryHome from "./BoundaryHome";
import ApprovedMicroplans from "./ApprovedMicroplans";
import FetchFromMicroplan from "../../components/fetchFromMicroplan";
+import Attendance from "./Attendance";
/**
* The CampaignBreadCrumb function generates breadcrumb navigation for a campaign setup page in a React
* application.
@@ -154,6 +155,8 @@ const App = ({ path, BOUNDARY_HIERARCHY_TYPE, hierarchyData }) => {
} />
} />
} />
+ } />
+