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 339 additions and 47 deletions
Showing only changes of commit 9bdcc74486 - Show all commits

View File

@ -12,7 +12,7 @@ import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager";
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
const Attendance = ({ getRole, handleModalData, searchTerm, filters }) => {
const queryClient = useQueryClient();
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
@ -50,22 +50,58 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
.filter((d) => d.activity === 0)
.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 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
let tempData = 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
);
});
}
// Organization filter
if (filters?.selectedOrganization) {
tempData = tempData.filter(
(item) => item.organization?.name === filters.selectedOrganization
);
});
}, [group1, group2, searchTerm]);
}
// Services filter
if (filters?.selectedServices?.length > 0) {
tempData = tempData.filter((item) =>
filters.selectedServices.includes(item.service?.name)
);
}
return tempData;
}, [group1, group2, searchTerm, filters]);
const { currentPage, totalPages, currentItems, paginate } = usePagination(
@ -116,7 +152,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
<>
<div
className="table-responsive text-nowrap h-100"
style={{ minHeight: "200px" }} // 🔹 Ensures fixed height
style={{ minHeight: "200px" }} // Ensures fixed height
>
<div className="d-flex text-start align-items-center py-2">
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>

View File

@ -33,7 +33,7 @@ const usePagination = (data, itemsPerPage) => {
};
};
const AttendanceLog = ({ handleModalData, searchTerm }) => {
const AttendanceLog = ({ handleModalData, searchTerm, filters }) => {
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
@ -139,16 +139,43 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
}, [data, showPending]);
// 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(() => {
if (!searchTerm) {
return processedData;
}
let tempData = processedData;
if (searchTerm) {
const lowercasedSearchTerm = searchTerm.toLowerCase();
return processedData.filter((item) => {
tempData = tempData.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [processedData, searchTerm]);
}
if (filters?.selectedOrganization) {
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 {
currentPage,
@ -344,7 +371,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
</tbody>
</table>
) : (
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
<div className="my-12"><span className="text-secondary">No data available for the selected date range. Please Select another date.</span></div>
)}
</div>
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (

View File

@ -10,7 +10,7 @@ import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
import { useQueryClient } from "@tanstack/react-query";
const Regularization = ({ handleRequest, searchTerm }) => {
const Regularization = ({ handleRequest, searchTerm,filters }) => {
const queryClient = useQueryClient();
// var selectedProject = useSelector((store) => store.localVariables.projectId);
const selectedProject = useSelectedProject();
@ -47,17 +47,47 @@ const Regularization = ({ handleRequest, searchTerm }) => {
);
// 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 sortedList = [...regularizesList].sort(sortByName);
if (!searchTerm) {
return sortedList;
let sortedList = [...regularizesList].sort(sortByName);
// Search filter
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
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 } =
usePagination(filteredSearchData, 20);

View File

@ -22,6 +22,7 @@ import GlobalModel from "../../components/common/GlobalModel";
import CheckCheckOutmodel from "../../components/Activities/CheckCheckOutForm";
import AttendLogs from "../../components/Activities/AttendLogs";
import { useQueryClient } from "@tanstack/react-query";
import FilterIconOrgServices from "./FilterIconOrgServices";
const AttendancePage = () => {
const [activeTab, setActiveTab] = useState("all");
@ -39,6 +40,10 @@ const AttendancePage = () => {
const [modelConfig, setModelConfig] = useState();
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE);
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
const [appliedFilters, setAppliedFilters] = useState({
selectedOrganization: "",
selectedServices: [],
});
const [formData, setFormData] = useState({
markTime: "",
@ -95,11 +100,11 @@ const AttendancePage = () => {
{(modelConfig?.action === 0 ||
modelConfig?.action === 1 ||
modelConfig?.action === 2) && (
<CheckCheckOutmodel
modeldata={modelConfig}
closeModal={closeModal}
/>
)}
<CheckCheckOutmodel
modeldata={modelConfig}
closeModal={closeModal}
/>
)}
{/* For view logs */}
{modelConfig?.action === 6 && (
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
@ -128,9 +133,8 @@ const AttendancePage = () => {
<li className="nav-item">
<button
type="button"
className={`nav-link ${
activeTab === "all" ? "active" : ""
} fs-6`}
className={`nav-link ${activeTab === "all" ? "active" : ""
} fs-6`}
onClick={() => handleTabChange("all")}
data-bs-toggle="tab"
data-bs-target="#navs-top-home"
@ -141,9 +145,8 @@ const AttendancePage = () => {
<li className="nav-item">
<button
type="button"
className={`nav-link ${
activeTab === "logs" ? "active" : ""
} fs-6`}
className={`nav-link ${activeTab === "logs" ? "active" : ""
} fs-6`}
onClick={() => handleTabChange("logs")}
data-bs-toggle="tab"
data-bs-target="#navs-top-profile"
@ -155,9 +158,8 @@ const AttendancePage = () => {
<li className={`nav-item ${!DoRegularized ? "d-none" : ""}`}>
<button
type="button"
className={`nav-link ${
activeTab === "regularization" ? "active" : ""
} fs-6`}
className={`nav-link ${activeTab === "regularization" ? "active" : ""
} fs-6`}
onClick={() => handleTabChange("regularization")}
data-bs-toggle="tab"
data-bs-target="#navs-top-messages"
@ -169,7 +171,10 @@ const AttendancePage = () => {
</div>
{/* Single search input that moves */}
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto px-2">
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center">
<FilterIconOrgServices
onApplyFilters={(filters) => setAppliedFilters(filters)}
/>
<input
type="text"
className="form-control form-control-sm"
@ -179,6 +184,7 @@ const AttendancePage = () => {
style={{ minWidth: "200px" }}
/>
</div>
</div>
</div>
@ -191,6 +197,7 @@ const AttendancePage = () => {
handleModalData={handleModalData}
getRole={getRole}
searchTerm={searchTerm}
filters={appliedFilters}
/>
</div>
)}
@ -199,12 +206,15 @@ const AttendancePage = () => {
<AttendanceLog
handleModalData={handleModalData}
searchTerm={searchTerm}
filters={appliedFilters}
/>
</div>
)}
{activeTab === "regularization" && DoRegularized && (
<div className="tab-pane fade show active py-0">
<Regularization searchTerm={searchTerm} />
<Regularization
searchTerm={searchTerm}
filters={appliedFilters} />
</div>
)}
</>

View File

@ -0,0 +1,189 @@
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;