diff --git a/src/pages/ImageGallery/ImageGalleryPage.jsx b/src/pages/ImageGallery/ImageGalleryPage.jsx index c34a8d71..70a9d37d 100644 --- a/src/pages/ImageGallery/ImageGalleryPage.jsx +++ b/src/pages/ImageGallery/ImageGalleryPage.jsx @@ -11,18 +11,15 @@ import { setProjectId } from "../../slices/localVariablesSlice"; import ImageGalleryListView from "../../components/ImageGallery/ImageGalleryListView"; import ImageGalleryFilters from "../../components/ImageGallery/ImageGalleryFilters"; import "../../components/ImageGallery/ImageGallery.css"; - -// --- NEW IMPORTS --- import { useFab } from "../../Context/FabContext"; -const SCROLL_THRESHOLD = 5; - const ImageGalleryPage = () => { - const selectedProjectId = useSelector((store) => store.localVariables.projectId); const dispatch = useDispatch(); + const selectedProjectId = useSelector((store) => store.localVariables.projectId); const { projectNames } = useProjectName(); const loaderRef = useRef(null); const { openModal } = useModal(); + const { setOffcanvasContent, setShowTrigger } = useFab(); // Auto-select first project if none selected useEffect(() => { @@ -31,14 +28,14 @@ const ImageGalleryPage = () => { } }, [selectedProjectId, projectNames, dispatch]); - // --- Filters --- + // --- Filters state --- const [appliedFilters, setAppliedFilters] = useState({ - buildingIds: null, - floorIds: null, - activityIds: null, - uploadedByIds: null, - workCategoryIds: null, - workAreaIds: null, + buildingIds: [], + floorIds: [], + activityIds: [], + uploadedByIds: [], + workCategoryIds: [], + workAreaIds: [], startDate: null, endDate: null, }); @@ -54,130 +51,201 @@ const ImageGalleryPage = () => { const images = data?.pages.flatMap((page) => page.data) || []; - // --- Utility: derive filter options --- - const getUniqueValues = useCallback( - (idKey, nameKey) => { - const m = new Map(); - images.forEach((batch) => { - const id = idKey === "floorIds" ? batch.floorIds : batch[idKey]; - if (id && batch[nameKey] && !m.has(id)) { - m.set(id, batch[nameKey]); - } - }); - return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1])); - }, - [images] - ); + // --- Utility: store mappings independent of images --- + const [labelMaps, setLabelMaps] = useState({ + buildings: new Map(), + floors: new Map(), + activities: new Map(), + workAreas: new Map(), + workCategories: new Map(), + uploadedByUsers: new Map(), + }); + + useEffect(() => { + const buildingsMap = new Map(labelMaps.buildings); + const floorsMap = new Map(labelMaps.floors); + const activitiesMap = new Map(labelMaps.activities); + const workAreasMap = new Map(labelMaps.workAreas); + const workCategoriesMap = new Map(labelMaps.workCategories); + const uploadedByMap = new Map(labelMaps.uploadedByUsers); - const getUploadedBy = useCallback(() => { - const m = new Map(); images.forEach((batch) => { - batch.documents.forEach((doc) => { - const name = `${doc.uploadedBy?.firstName || ""} ${ - doc.uploadedBy?.lastName || "" - }`.trim(); - if (doc.uploadedBy?.id && name && !m.has(doc.uploadedBy.id)) { - m.set(doc.uploadedBy.id, name); - } + if (batch.buildingId && batch.buildingName) buildingsMap.set(batch.buildingId, batch.buildingName); + if (batch.floorIds && batch.floorName) floorsMap.set(batch.floorIds, batch.floorName); + if (batch.activityId && batch.activityName) activitiesMap.set(batch.activityId, batch.activityName); + if (batch.workAreaId && batch.workAreaName) workAreasMap.set(batch.workAreaId, batch.workAreaName); + if (batch.workCategoryId && batch.workCategoryName) workCategoriesMap.set(batch.workCategoryId, batch.workCategoryName); + batch.documents?.forEach((doc) => { + const name = `${doc.uploadedBy?.firstName || ""} ${doc.uploadedBy?.lastName || ""}`.trim(); + if (doc.uploadedBy?.id && name) uploadedByMap.set(doc.uploadedBy.id, name); }); }); - return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1])); + + setLabelMaps({ + buildings: buildingsMap, + floors: floorsMap, + activities: activitiesMap, + workAreas: workAreasMap, + workCategories: workCategoriesMap, + uploadedByUsers: uploadedByMap, + }); }, [images]); - const buildings = getUniqueValues("buildingId", "buildingName"); - const floors = getUniqueValues("floorIds", "floorName"); - const activities = getUniqueValues("activityId", "activityName"); - const workAreas = getUniqueValues("workAreaId", "workAreaName"); - const workCategories = getUniqueValues("workCategoryId", "workCategoryName"); - const uploadedByUsers = getUploadedBy(); + // --- Apply filters --- + const handleApplyFilters = useCallback((values) => setAppliedFilters(values), []); - // --- Apply filters callback --- - const handleApplyFilters = useCallback((values) => { - setAppliedFilters(values); - }, []); + // --- Remove single filter --- + const handleRemoveFilter = (filterKey, valueId) => { + setAppliedFilters((prev) => { + const updated = { ...prev }; + if (Array.isArray(updated[filterKey])) { + updated[filterKey] = updated[filterKey].filter((id) => id !== valueId); + } else if (filterKey === "startDate" || filterKey === "endDate") { + updated[filterKey] = null; + } + return updated; + }); + }; - // --- Filter Panel Memoization --- + // --- Chips --- + const appliedFiltersChips = useMemo(() => { + const chips = []; + const { buildings, floors, activities, workAreas, workCategories, uploadedByUsers } = labelMaps; + + appliedFilters.buildingIds?.forEach((id) => + chips.push({ label: "Building", value: buildings.get(id) || id, key: "buildingIds", id }) + ); + appliedFilters.floorIds?.forEach((id) => + chips.push({ label: "Floor", value: floors.get(id) || id, key: "floorIds", id }) + ); + appliedFilters.workAreaIds?.forEach((id) => + chips.push({ label: "Work Area", value: workAreas.get(id) || id, key: "workAreaIds", id }) + ); + appliedFilters.activityIds?.forEach((id) => + chips.push({ label: "Activity", value: activities.get(id) || id, key: "activityIds", id }) + ); + appliedFilters.uploadedByIds?.forEach((id) => + chips.push({ label: "Uploaded By", value: uploadedByUsers.get(id) || id, key: "uploadedByIds", id }) + ); + appliedFilters.workCategoryIds?.forEach((id) => + chips.push({ label: "Work Category", value: workCategories.get(id) || id, key: "workCategoryIds", id }) + ); + if (appliedFilters.startDate || appliedFilters.endDate) { + const start = appliedFilters.startDate ? moment(appliedFilters.startDate).format("DD MMM, YYYY") : ""; + const end = appliedFilters.endDate ? moment(appliedFilters.endDate).format("DD MMM, YYYY") : ""; + chips.push({ label: "Date Range", value: `${start} - ${end}`, key: "dateRange" }); + } + return chips; + }, [appliedFilters, labelMaps]); + + // --- Refetch on filter change --- + useEffect(() => { refetch(); }, [appliedFilters, refetch]); + + // --- Filter Panel --- const filterPanelElement = useMemo( () => ( ), - [ - buildings, - floors, - activities, - workAreas, - workCategories, - uploadedByUsers, - appliedFilters, - handleApplyFilters, - ] + [labelMaps, appliedFilters, handleApplyFilters] ); - // --- Fab Offcanvas Integration --- - const { setOffcanvasContent, setShowTrigger } = useFab(); + // --- Fab Offcanvas --- useEffect(() => { setShowTrigger(true); setOffcanvasContent("Gallery Filters", filterPanelElement); - return () => { setShowTrigger(false); setOffcanvasContent("", null); }; }, [filterPanelElement, setOffcanvasContent, setShowTrigger]); - // --- Refetch on project or filters --- + // --- EventBus --- useEffect(() => { - if (selectedProjectId) refetch(); - }, [selectedProjectId, appliedFilters, refetch]); - - // --- EventBus Refetch --- - useEffect(() => { - const handler = (data) => { - if (data.projectId === selectedProjectId) refetch(); - }; + const handler = (data) => { if (data.projectId === selectedProjectId) refetch(); }; eventBus.on("image_gallery", handler); return () => eventBus.off("image_gallery", handler); }, [selectedProjectId, refetch]); - // --- Infinite scroll observer --- + // --- Infinite scroll --- useEffect(() => { if (!loaderRef.current) return; - const observer = new IntersectionObserver( ([entry]) => { - if ( - entry.isIntersecting && - hasNextPage && - !isFetchingNextPage && - !isLoading - ) { - fetchNextPage(); - } + if (entry.isIntersecting && hasNextPage && !isFetchingNextPage && !isLoading) fetchNextPage(); }, { rootMargin: "200px", threshold: 0.1 } ); - observer.observe(loaderRef.current); - - return () => { - if (loaderRef.current) { - observer.unobserve(loaderRef.current); - } - }; + return () => loaderRef.current && observer.unobserve(loaderRef.current); }, [hasNextPage, isFetchingNextPage, isLoading, fetchNextPage]); return (
+ + {appliedFiltersChips.length > 0 && ( +
+ Filters: + + {/* Group chips by label */} + {["Building", "Floor", "Work Area", "Activity", "Uploaded By", "Work Category"].map((label) => { + const chipsForLabel = appliedFiltersChips.filter((chip) => chip.label === label); + if (chipsForLabel.length === 0) return null; + + return ( + + {label} : + {chipsForLabel.map((chip, idx) => ( + + {chip.value} +
+ )} + + { hasNextPage={hasNextPage} loaderRef={loaderRef} openModal={openModal} - SCROLL_THRESHOLD={SCROLL_THRESHOLD} formatUTCToLocalTime={formatUTCToLocalTime} moment={moment} />