diff --git a/health/micro-ui/web/micro-ui-internals/example/public/index.html b/health/micro-ui/web/micro-ui-internals/example/public/index.html index e27792f67b3..db4f9dc3d1c 100644 --- a/health/micro-ui/web/micro-ui-internals/example/public/index.html +++ b/health/micro-ui/web/micro-ui-internals/example/public/index.html @@ -12,7 +12,7 @@ DIGIT - + 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/package.json b/health/micro-ui/web/micro-ui-internals/packages/css/package.json index f5f09e35c1e..2d0ee8087ca 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/css/package.json +++ b/health/micro-ui/web/micro-ui-internals/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@egovernments/digit-ui-health-css", - "version": "0.2.34", + "version": "0.2.35", "license": "MIT", "main": "dist/index.css", "author": "Jagankumar ", @@ -66,4 +66,4 @@ "digit-ui", "css" ] -} \ No newline at end of file +} 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 5c924cb0e55..abcc0b351b8 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 @@ -231,4 +231,61 @@ > 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: 1.5rem; + background-color: #f3f3f3; + border-radius: 1rem; + 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: 1rem; + color: #fff; + font-weight: bold; + pointer-events: none; +} + +.assign-users-popup { + .digit-popup-heading{ + font-family: 'Roboto Condensed', sans-serif !important; + } + } 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..e587dc57e83 --- /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('REGISTER_NAME'), + selector: row => row.name?.givenName || '', + sortable: true, + }, + { + name: t('REGISTER_MOBILE_NUMBER'), + selector: row => row.mobileNumber || '', + sortable: true, + }, + { + name: t("REGISTER_USER_TYPE"), + selector: row => row?.userDetails?.type || '', + sortable: true + }, + { + name: t('REGISTER_ASSIGNED_SUPERVISOR'), + selector: row => row.isAssigned ? supervisorName : t('REGISTER_SUPERVISOR_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('REGISTER_SUPERVISOR_NOT_ASSIGNED')} + )} +
+ ); + }, + }, + { + name: t('REGISTER_ACTIONS'), + cell: row => ( +
+ {row.isAssigned ? ( +
+ ) + }, + ]; + + return ( +
+ {isLoading && } + {!isLoading && ( +
+ +
+
{t("REGISTER_USER_NAME")}
+ +
+
+ + } + pagination + paginationServer + customStyles={tableCustomStyle} + paginationTotalRows={totalRows} + onChangePage={handlePaginationChange} + onChangeRowsPerPage={handleRowsPerPageChange} + paginationPerPage={rowsPerPage} + sortIcon={} + paginationRowsPerPageOptions={[5, 10, 15, 20]} + noDataComponent={
{t('REGISTER_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..ef3b6d14e3e 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,32 +1,19 @@ 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: { - minHeight: "56px", + minHeight: "3.5rem", // 56px }, }, rows: { @@ -35,69 +22,48 @@ export const tableCustomStyle = { "&:hover": { backgroundColor: "#FBEEE8", }, + borderBottom: "0.0625rem solid #D6D5D4", // 1px }, }, headRow: { style: { - borderTopStyle: "solid", - borderTopWidth: "1px", - borderTopColor: "#D6D5D4", + borderBottom: "0.0625rem solid #D6D5D4", // 1px 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", - fontSize: "16px", + fontSize: "1rem", // 16px color: "#0B4B66", - padding: "16px", - lineHeight: "1.14rem", - zIndex: 10, + padding: "1rem", // 16px + lineHeight: "1.14rem", // 18.24px + borderBottom: "0.0625rem solid #D6D5D4", // 1px + borderRight: "none", }, }, cells: { style: { - "&:first-of-type": { - borderLeftStyle: "solid", - borderLeftWidth: "1px", - borderLeftColor: "#D6D5D4", - }, - borderRightStyle: "solid", - borderRightWidth: "1px", - borderRightColor: "#D6D5D4", color: "#363636", fontFamily: "Roboto", fontStyle: "normal", fontWeight: 400, - lineHeight: "1.37rem", + lineHeight: "1.37rem", // 21.92px textAlign: "left", - fontSize: "16px", - padding: "16px", + fontSize: "1rem", // 16px + padding: "1rem", // 16px + borderBottom: "0.0625rem solid #D6D5D4", // 1px + borderRight: "none", }, pagination: { style: { - marginTop: "-60px", + marginTop: "-3.75rem", // -60px borderStyle: "solid", - borderWidth: "1px", + borderWidth: "0.0625rem", // 1px borderColor: "#D6D5D4", - borderTopWidth: "0px", + borderTopWidth: "0", }, }, }, @@ -105,28 +71,20 @@ export const tableCustomStyle = { export const getTableCustomStyle = (freezeFirstColumn = false) => ({ tableWrapper: { - style: { - // overflow: "scroll", - }, + style: {}, }, table: { - style: { - // overflow: "scroll", - }, + style: {}, }, responsiveWrapper: { - style: { - // overflow: "scroll", - }, + style: {}, }, contextMenu: { - style: { - // overflow: "scroll", - }, + style: {}, }, header: { style: { - minHeight: "56px", + minHeight: "3.5rem", // 56px }, }, rows: { @@ -140,7 +98,7 @@ export const getTableCustomStyle = (freezeFirstColumn = false) => ({ headRow: { style: { borderTopStyle: "solid", - borderTopWidth: "1px", + borderTopWidth: "0.0625rem", // 1px borderTopColor: "#D6D5D4", backgroundColor: "#EEEEEE", }, @@ -150,9 +108,9 @@ export const getTableCustomStyle = (freezeFirstColumn = false) => ({ "&:first-of-type": { ...(freezeFirstColumn && { borderLeftStyle: "solid", - borderLeftWidth: "1px", + borderLeftWidth: "0.0625rem", // 1px borderLeftColor: "#D6D5D4", - borderTopLeftRadius: "0.25rem", + borderTopLeftRadius: "0.25rem", // 4px position: "sticky", left: 0, backgroundColor: "#EEEEEE", @@ -161,20 +119,20 @@ export const getTableCustomStyle = (freezeFirstColumn = false) => ({ }, "&:last-of-type": { borderLeftStyle: "solid", - borderLeftWidth: "1px", + borderLeftWidth: "0.0625rem", // 1px borderLeftColor: "#D6D5D4", - borderTopRightRadius: "0.25rem", + borderTopRightRadius: "0.25rem", // 4px }, borderRightStyle: "solid", - borderRightWidth: "1px", + borderRightWidth: "0.0625rem", // 1px borderRightColor: "#D6D5D4", fontFamily: "Roboto", fontWeight: "700", fontStyle: "normal", - fontSize: "16px", + fontSize: "1rem", // 16px color: "#0B4B66", - padding: "16px", - lineHeight: "1.14rem", + padding: "1rem", // 16px + lineHeight: "1.14rem", // 18.24px }, }, cells: { @@ -182,35 +140,36 @@ export const getTableCustomStyle = (freezeFirstColumn = false) => ({ "&:first-of-type": { ...(freezeFirstColumn && { borderLeftStyle: "solid", - borderLeftWidth: "1px", + borderLeftWidth: "0.0625rem", // 1px borderLeftColor: "#D6D5D4", position: "sticky", left: 0, backgroundColor: "#FFFFFF", zIndex: 1, - boxShadow: "2px 0 5px rgba(0, 0, 0, 0.1)", + boxShadow: "0.125rem 0 0.3125rem rgba(0, 0, 0, 0.1)", // 2px 0 5px }), }, borderRightStyle: "solid", - borderRightWidth: "1px", + borderRightWidth: "0.0625rem", // 1px borderRightColor: "#D6D5D4", color: "#363636", fontFamily: "Roboto", fontStyle: "normal", fontWeight: 400, - lineHeight: "1.37rem", + lineHeight: "1.37rem", // 21.92px textAlign: "left", - fontSize: "16px", - padding: "16px", + fontSize: "1rem", // 16px + padding: "1rem", // 16px }, pagination: { style: { - marginTop: "-60px", + marginTop: "-3.75rem", // -60px borderStyle: "solid", - borderWidth: "1px", + borderWidth: "0.0625rem", // 1px borderColor: "#D6D5D4", - borderTopWidth: "0px", + borderTopWidth: "0", }, }, }, }); + 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..404e183d326 --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/Attendance.js @@ -0,0 +1,340 @@ +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("REGISTER_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("REGISTER_EMAIL"), + selector: (row) => { + return ( +
+ {row.email || t("NA")} +
+ ); + }, + sortable: false, + }, + { + name: t("REGISTER_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("REGISTER_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 && ( +
+ {chipPopUpRowId === row.id && ( + + )} +
+ )} + {userDetails?.staffAttendeeMap[row?.id]?.attendees.length === 0 && ( +
+ {t("REGISTER_USERS_NOT_ASSIGNED")} +
+ )} +
+ + ); + }, + }, + { + name: t("REGISTER_ACTION"), + sortable: false, + cell: (row) => { + return ( +