Adding Filter Icon in Attendance tab and add functionality in all Attendance component. #416

Merged
vikas.nale merged 2 commits from Kartik_Task_AttFilter#1235 into Organization_Management 2025-09-20 06:45:12 +00:00
5 changed files with 143 additions and 314 deletions
Showing only changes of commit 90b96864be - Show all commits

View File

@ -12,7 +12,7 @@ import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
const Attendance = ({ getRole, handleModalData, searchTerm, filters }) => { const Attendance = ({ getRole, handleModalData, searchTerm }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -50,57 +50,57 @@ const Attendance = ({ getRole, handleModalData, searchTerm, filters }) => {
.filter((d) => d.activity === 0) .filter((d) => d.activity === 0)
.sort(sortByName); .sort(sortByName);
// const finalFilteredData = useMemo(() => {
// const combinedData = [...group1, ...group2];
// if (!searchTerm) {
// return combinedData;
// }
// const lowercasedSearchTerm = searchTerm.toLowerCase();
// return combinedData.filter((item) => {
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
// const role = item.jobRoleName?.toLowerCase() || "";
// return (
// fullName.includes(lowercasedSearchTerm) ||
// role.includes(lowercasedSearchTerm) // also search by role
// );
// });
// }, [group1, group2, searchTerm]);
const finalFilteredData = useMemo(() => { const finalFilteredData = useMemo(() => {
const combinedData = [...group1, ...group2]; const combinedData = [...group1, ...group2];
if (!searchTerm) {
let tempData = combinedData; return combinedData;
// Search filter
if (searchTerm) {
const lowercasedSearchTerm = searchTerm.toLowerCase();
tempData = tempData.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
const role = item.jobRoleName?.toLowerCase() || "";
return (
fullName.includes(lowercasedSearchTerm) ||
role.includes(lowercasedSearchTerm) // also search by role
);
});
} }
const lowercasedSearchTerm = searchTerm.toLowerCase();
// Organization filter return combinedData.filter((item) => {
if (filters?.selectedOrganization) { const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
tempData = tempData.filter( const role = item.jobRoleName?.toLowerCase() || "";
(item) => item.organization?.name === filters.selectedOrganization return (
fullName.includes(lowercasedSearchTerm) ||
role.includes(lowercasedSearchTerm) // also search by role
); );
} });
}, [group1, group2, searchTerm]);
// Services filter // const finalFilteredData = useMemo(() => {
if (filters?.selectedServices?.length > 0) { // const combinedData = [...group1, ...group2];
tempData = tempData.filter((item) =>
filters.selectedServices.includes(item.service?.name)
);
}
return tempData; // let tempData = combinedData;
}, [group1, group2, searchTerm, filters]);
// // Search filter
// if (searchTerm) {
// const lowercasedSearchTerm = searchTerm.toLowerCase();
// tempData = tempData.filter((item) => {
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
// const role = item.jobRoleName?.toLowerCase() || "";
// return (
// fullName.includes(lowercasedSearchTerm) ||
// role.includes(lowercasedSearchTerm) // also search by role
// );
// });
// }
// // Organization filter
// if (filters?.selectedOrganization) {
// tempData = tempData.filter(
// (item) => item.organization?.name === filters.selectedOrganization
// );
// }
// // Services filter
// if (filters?.selectedServices?.length > 0) {
// tempData = tempData.filter((item) =>
// filters.selectedServices.includes(item.service?.name)
// );
// }
// return tempData;
// }, [group1, group2, searchTerm, filters]);

View File

@ -33,7 +33,7 @@ const usePagination = (data, itemsPerPage) => {
}; };
}; };
const AttendanceLog = ({ handleModalData, searchTerm, filters }) => { const AttendanceLog = ({ handleModalData, searchTerm }) => {
// const selectedProject = useSelector( // const selectedProject = useSelector(
// (store) => store.localVariables.projectId // (store) => store.localVariables.projectId
// ); // );
@ -139,42 +139,42 @@ const AttendanceLog = ({ handleModalData, searchTerm, filters }) => {
}, [data, showPending]); }, [data, showPending]);
// New useEffect to handle search filtering // New useEffect to handle search filtering
// const filteredSearchData = useMemo(() => {
// if (!searchTerm) {
// return processedData;
// }
// const lowercasedSearchTerm = searchTerm.toLowerCase();
// return processedData.filter((item) => {
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
// return fullName.includes(lowercasedSearchTerm);
// });
// }, [processedData, searchTerm]);
const filteredSearchData = useMemo(() => { const filteredSearchData = useMemo(() => {
let tempData = processedData; if (!searchTerm) {
return processedData;
if (searchTerm) { }
const lowercasedSearchTerm = searchTerm.toLowerCase(); const lowercasedSearchTerm = searchTerm.toLowerCase();
tempData = tempData.filter((item) => { return processedData.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase(); const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm); return fullName.includes(lowercasedSearchTerm);
}); });
} }, [processedData, searchTerm]);
if (filters?.selectedOrganization) { // const filteredSearchData = useMemo(() => {
tempData = tempData.filter( // let tempData = processedData;
(item) => item.organization?.name === filters.selectedOrganization
);
}
if (filters?.selectedServices?.length > 0) { // if (searchTerm) {
tempData = tempData.filter((item) => // const lowercasedSearchTerm = searchTerm.toLowerCase();
filters.selectedServices.includes(item.service?.name) // tempData = tempData.filter((item) => {
); // const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
} // return fullName.includes(lowercasedSearchTerm);
// });
// }
return tempData; // if (filters?.selectedOrganization) {
}, [processedData, searchTerm, filters]); // tempData = tempData.filter(
// (item) => item.organization?.name === filters.selectedOrganization
// );
// }
// if (filters?.selectedServices?.length > 0) {
// tempData = tempData.filter((item) =>
// filters.selectedServices.includes(item.service?.name)
// );
// }
// return tempData;
// }, [processedData, searchTerm, filters]);
const { const {
@ -269,14 +269,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, filters }) => {
<label className="form-check-label ms-0">Show Pending</label> <label className="form-check-label ms-0">Show Pending</label>
</div> </div>
</div> </div>
<div className="col-md-2 m-0 text-end">
<i
className={`bx bx-refresh cursor-pointer fs-4 ${isFetching ? "spin" : ""
}`}
title="Refresh"
onClick={() => refetch()}
/>
</div>
</div> </div>
<div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}> <div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}>
{isLoading ? ( {isLoading ? (

View File

@ -10,7 +10,7 @@ import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager"; import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
const Regularization = ({ handleRequest, searchTerm,filters }) => { const Regularization = ({ handleRequest, searchTerm }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// var selectedProject = useSelector((store) => store.localVariables.projectId); // var selectedProject = useSelector((store) => store.localVariables.projectId);
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
@ -47,46 +47,46 @@ const Regularization = ({ handleRequest, searchTerm,filters }) => {
); );
// Filter the data based on the search term and sort it // Filter the data based on the search term and sort it
// const filteredSearchData = useMemo(() => {
// const sortedList = [...regularizesList].sort(sortByName);
// if (!searchTerm) {
// return sortedList;
// }
// const lowercasedSearchTerm = searchTerm.toLowerCase();
// return sortedList.filter((item) => {
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
// return fullName.includes(lowercasedSearchTerm);
// });
// }, [regularizesList, searchTerm]);
const filteredSearchData = useMemo(() => { const filteredSearchData = useMemo(() => {
let sortedList = [...regularizesList].sort(sortByName); const sortedList = [...regularizesList].sort(sortByName);
if (!searchTerm) {
// Search filter return sortedList;
if (searchTerm) {
const lowercasedSearchTerm = searchTerm.toLowerCase();
sortedList = sortedList.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
} }
const lowercasedSearchTerm = searchTerm.toLowerCase();
return sortedList.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [regularizesList, searchTerm]);
// Organization filter // const filteredSearchData = useMemo(() => {
if (filters?.selectedOrganization) { // let sortedList = [...regularizesList].sort(sortByName);
sortedList = sortedList.filter(
(item) => item.organization?.name === filters.selectedOrganization
);
}
// Services filter // // Search filter
if (filters?.selectedServices?.length > 0) { // if (searchTerm) {
sortedList = sortedList.filter((item) => // const lowercasedSearchTerm = searchTerm.toLowerCase();
filters.selectedServices.includes(item.service?.name) // sortedList = sortedList.filter((item) => {
); // const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
} // return fullName.includes(lowercasedSearchTerm);
// });
// }
return sortedList; // // Organization filter
}, [regularizesList, searchTerm, filters]); // if (filters?.selectedOrganization) {
// sortedList = sortedList.filter(
// (item) => item.organization?.name === filters.selectedOrganization
// );
// }
// // Services filter
// if (filters?.selectedServices?.length > 0) {
// sortedList = sortedList.filter((item) =>
// filters.selectedServices.includes(item.service?.name)
// );
// }
// return sortedList;
// }, [regularizesList, searchTerm, filters]);
const { currentPage, totalPages, currentItems, paginate } = const { currentPage, totalPages, currentItems, paginate } =

View File

@ -22,7 +22,6 @@ import GlobalModel from "../../components/common/GlobalModel";
import CheckCheckOutmodel from "../../components/Activities/CheckCheckOutForm"; import CheckCheckOutmodel from "../../components/Activities/CheckCheckOutForm";
import AttendLogs from "../../components/Activities/AttendLogs"; import AttendLogs from "../../components/Activities/AttendLogs";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import FilterIconOrgServices from "./FilterIconOrgServices";
const AttendancePage = () => { const AttendancePage = () => {
const [activeTab, setActiveTab] = useState("all"); const [activeTab, setActiveTab] = useState("all");
@ -171,10 +170,38 @@ const AttendancePage = () => {
</div> </div>
{/* Single search input that moves */} {/* Single search input that moves */}
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center"> {/* <div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center">
<FilterIconOrgServices <input
onApplyFilters={(filters) => setAppliedFilters(filters)} type="text"
className="form-control form-control-sm"
placeholder="Search Employee..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ minWidth: "200px" }}
/> />
</div> */}
{/* Search + Organization filter */}
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center">
{/* Organization Dropdown */}
<select
className="form-select form-select-sm"
style={{ minWidth: "180px" }}
value={appliedFilters.selectedOrganization}
onChange={(e) =>
setAppliedFilters((prev) => ({
...prev,
selectedOrganization: e.target.value,
}))
}
>
<option value="">All Organizations</option>
<option value="Org A">Org A</option>
<option value="Org B">Org B</option>
<option value="Org C">Org C</option>
</select>
{/* Search Input */}
<input <input
type="text" type="text"
className="form-control form-control-sm" className="form-control form-control-sm"
@ -197,7 +224,6 @@ const AttendancePage = () => {
handleModalData={handleModalData} handleModalData={handleModalData}
getRole={getRole} getRole={getRole}
searchTerm={searchTerm} searchTerm={searchTerm}
filters={appliedFilters}
/> />
</div> </div>
)} )}
@ -206,7 +232,6 @@ const AttendancePage = () => {
<AttendanceLog <AttendanceLog
handleModalData={handleModalData} handleModalData={handleModalData}
searchTerm={searchTerm} searchTerm={searchTerm}
filters={appliedFilters}
/> />
</div> </div>
)} )}
@ -214,7 +239,7 @@ const AttendancePage = () => {
<div className="tab-pane fade show active py-0"> <div className="tab-pane fade show active py-0">
<Regularization <Regularization
searchTerm={searchTerm} searchTerm={searchTerm}
filters={appliedFilters} /> />
</div> </div>
)} )}
</> </>

View File

@ -1,189 +0,0 @@
import React, { useState, useEffect } from "react";
import { useSelectedProject } from "../../slices/apiDataManager";
import { useProjectAssignedServices } from "../../hooks/useProjects";
const FilterIconOrgServices = ({ onApplyFilters }) => {
const selectedProject = useSelectedProject();
// Fetch services from API
const { data: servicesData = [], isLoading } = useProjectAssignedServices(selectedProject);
// Hardcoded organization list
const organizations = ["Org A", "Org B", "Org C"];
const [selectedOrganization, setSelectedOrganization] = useState("");
const [selectedServices, setSelectedServices] = useState([]);
const [appliedOrganization, setAppliedOrganization] = useState("");
const [appliedServices, setAppliedServices] = useState([]);
useEffect(() => {
setSelectedOrganization("");
setSelectedServices([]);
setAppliedOrganization("");
setAppliedServices([]);
}, [selectedProject]);
const handleFilterChange = (type, value) => {
if (type === "organization") {
setSelectedOrganization(value);
} else if (type === "service") {
setSelectedServices((prev) =>
prev.includes(value) ? prev.filter((s) => s !== value) : [...prev, value]
);
}
};
const applyFilters = () => {
setAppliedOrganization(selectedOrganization);
setAppliedServices(selectedServices);
onApplyFilters({
selectedOrganization,
selectedServices,
});
document.getElementById("filterDropdown").click();
};
const clearAllFilters = () => {
setSelectedOrganization("");
setSelectedServices([]);
setAppliedOrganization("");
setAppliedServices([]);
onApplyFilters({
selectedOrganization: "",
selectedServices: [],
});
};
const appliedFilterCount =
(appliedOrganization ? 1 : 0) + appliedServices.length;
return (
<div className="dropdown" style={{ marginLeft: "-14px", position: "relative" }}>
<a
className="dropdown-toggle hide-arrow cursor-pointer"
id="filterDropdown"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<div style={{ position: "relative", display: "inline-block" }}>
<i
className="bx bx-slider-alt"
style={{
color: appliedFilterCount > 0 ? "#7161EF" : "gray",
fontSize: "20px",
}}
></i>
{appliedFilterCount > 0 && (
<span
style={{
position: "absolute",
top: "-11px",
right: "-6px",
backgroundColor: "#FFC107",
color: "white",
fontSize: "10px",
fontWeight: "bold",
borderRadius: "50%",
width: "18px",
height: "18px",
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "1px solid white",
}}
>
{appliedFilterCount}
</span>
)}
</div>
</a>
<ul
className="dropdown-menu p-2 mt-2"
aria-labelledby="filterDropdown"
style={{ minWidth: "300px", fontSize: "13px" }}
onClick={(e) => e.stopPropagation()}
>
{/* Organization */}
<li>
<div className="fw-bold text-dark mb-3 mt-3">Organization</div>
<select
className="form-select form-select-sm"
value={selectedOrganization}
onChange={(e) => handleFilterChange("organization", e.target.value)}
>
<option value="">Select Organization</option>
{organizations.map((org, idx) => (
<option key={idx} value={org}>
{org}
</option>
))}
</select>
</li>
{/* Services */}
<li><hr className="my-1" /></li>
<li>
<div className="fw-bold text-dark mb-3 mt-5">Services</div>
<div className="row">
{isLoading ? (
<div className="col-12 text-muted">Loading services...</div>
) : servicesData.length > 0 ? (
servicesData.map((service, idx) => (
<div className="col-6" key={idx}>
<div className="form-check mb-1">
<input
className="form-check-input"
type="checkbox"
id={`service-${service.name || service}`}
checked={selectedServices.includes(service.name || service)}
onChange={() => handleFilterChange("service", service.name || service)}
/>
<label
className="form-check-label"
htmlFor={`service-${service.name || service}`}
>
{service.name || service}
</label>
</div>
</div>
))
) : (
<div className="col-12 text-muted">No services found.</div>
)}
</div>
</li>
{/* Actions */}
<li><hr className="my-1" /></li>
{(appliedFilterCount > 0 ||
selectedOrganization ||
selectedServices.length > 0) && (
<li className="d-flex justify-content-end gap-2 px-2 mt-5 mb-2">
<button
type="button"
className="btn btn-label-secondary btn-sm py-0 px-2"
onClick={clearAllFilters}
>
Clear
</button>
<button
type="button"
className="btn btn-primary btn-sm py-0 px-2"
onClick={applyFilters}
>
Apply
</button>
</li>
)}
</ul>
</div>
);
};
export default FilterIconOrgServices;