import React, { useState, useEffect, useRef, useCallback } from "react"; import "./ImageGallery.css"; import moment from "moment"; import { useSelector } from "react-redux"; import { useModal } from "./ModalContext"; import ImagePop from "./ImagePop"; import Avatar from "../../components/common/Avatar"; import DateRangePicker from "../../components/common/DateRangePicker"; import eventBus from "../../services/eventBus"; import Breadcrumb from "../../components/common/Breadcrumb"; import {formatUTCToLocalTime} from "../../utils/dateUtils"; import DateRangePickerNoDefault from "./DateRangePickerNoDefault"; const SCROLL_THRESHOLD = 5; const ImageGallery = () => { const selectedProjectId = useSelector((store) => store.localVariables.projectId); const { images, allImagesData, pageNumber, setPageNumber, hasMore, loading, loadingMore, fetchImages, resetGallery, } = useImageGallery(selectedProjectId); const { openModal } = useModal(); const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); const [selectedFilters, setSelectedFilters] = useState({ building: [], floor: [], activity: [], uploadedBy: [], workCategory: [], workArea: [], startDate: "", endDate: "", }); const [appliedFilters, setAppliedFilters] = useState({ buildingIds: null, floorIds: null, activityIds: null, uploadedByIds: null, workCategoryIds: null, workAreaIds: null, startDate: null, endDate: null, }); const [collapsedFilters, setCollapsedFilters] = useState({ dateRange: false, building: false, floor: false, activity: false, uploadedBy: false, workCategory: false, workArea: false, }); const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false); const [hoveredImage, setHoveredImage] = useState(null); const imageGroupRefs = useRef({}); const loaderRef = useRef(null); const filterPanelRef = useRef(null); const filterButtonRef = useRef(null); useEffect(() => { const handleClickOutside = (event) => { if ( filterPanelRef.current && !filterPanelRef.current.contains(event.target) && filterButtonRef.current && !filterButtonRef.current.contains(event.target) ) { setIsFilterPanelOpen(false); } }; if (isFilterPanelOpen) { document.addEventListener("mousedown", handleClickOutside); } else { document.removeEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isFilterPanelOpen]); useEffect(() => { if (!selectedProjectId) { resetGallery(); return; } resetGallery(); fetchImages(1, appliedFilters, true); }, [selectedProjectId, appliedFilters]); useEffect(() => { const handleExternalEvent = (data) => { if (selectedProjectId === data.projectId) { resetGallery(); fetchImages(1, appliedFilters, true); } }; eventBus.on("image_gallery", handleExternalEvent); return () => { eventBus.off("image_gallery", handleExternalEvent); }; }, [appliedFilters, fetchImages, selectedProjectId]); useEffect(() => { if (!loaderRef.current) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !loadingMore && !loading) { setPageNumber((prevPageNumber) => prevPageNumber + 1); } }, { root: null, rootMargin: "200px", threshold: 0.1, } ); observer.observe(loaderRef.current); return () => { if (loaderRef.current) { observer.unobserve(loaderRef.current); } }; }, [hasMore, loadingMore, loading]); useEffect(() => { if (pageNumber > 1) { fetchImages(pageNumber, appliedFilters); } }, [pageNumber]); const getUniqueValuesWithIds = useCallback((idKey, nameKey) => { const map = new Map(); allImagesData.forEach(batch => { let id = idKey === "floorIds" ? batch.floorIds : batch[idKey]; const name = batch[nameKey]; if (id && name && !map.has(id)) { map.set(id, name); } }); return Array.from(map.entries()).sort((a, b) => a[1].localeCompare(b[1])); }, [allImagesData]); const getUniqueUploadedByUsers = useCallback(() => { const uniqueUsersMap = new Map(); allImagesData.forEach(batch => { batch.documents.forEach(doc => { if (doc.uploadedBy && doc.uploadedBy.id) { const fullName = `${doc.uploadedBy.firstName || ""} ${doc.uploadedBy.lastName || ""}`.trim(); if (fullName) { uniqueUsersMap.set(doc.uploadedBy.id, fullName); } } }); }); return Array.from(uniqueUsersMap.entries()).sort((a, b) => a[1].localeCompare(b[1])); }, [allImagesData]); const buildings = getUniqueValuesWithIds("buildingId", "buildingName"); const floors = getUniqueValuesWithIds("floorIds", "floorName"); const activities = getUniqueValuesWithIds("activityId", "activityName"); const workAreas = getUniqueValuesWithIds("workAreaId", "workAreaName"); const uploadedByUsers = getUniqueUploadedByUsers(); const workCategories = getUniqueValuesWithIds("workCategoryId", "workCategoryName"); const toggleFilter = useCallback((type, itemId, itemName) => { setSelectedFilters((prev) => { const current = prev[type]; const isSelected = current.some((item) => item[0] === itemId); const newArray = isSelected ? current.filter((item) => item[0] !== itemId) : [...current, [itemId, itemName]]; return { ...prev, [type]: newArray, }; }); }, []); const setDateRange = useCallback(({ startDate, endDate }) => { setSelectedFilters((prev) => ({ ...prev, startDate: startDate || "", endDate: endDate || "", })); }, []); const toggleCollapse = useCallback((type) => { setCollapsedFilters((prev) => ({ ...prev, [type]: !prev[type], })); }, []); const handleApplyFilters = useCallback(() => { const payload = { buildingIds: selectedFilters.building.length ? selectedFilters.building.map((item) => item[0]) : null, floorIds: selectedFilters.floor.length ? selectedFilters.floor.map((item) => item[0]) : null, workAreaIds: selectedFilters.workArea.length ? selectedFilters.workArea.map((item) => item[0]) : null, workCategoryIds: selectedFilters.workCategory.length ? selectedFilters.workCategory.map((item) => item[0]) : null, activityIds: selectedFilters.activity.length ? selectedFilters.activity.map((item) => item[0]) : null, uploadedByIds: selectedFilters.uploadedBy.length ? selectedFilters.uploadedBy.map((item) => item[0]) : null, startDate: selectedFilters.startDate || null, endDate: selectedFilters.endDate || null, }; const areFiltersChanged = Object.keys(payload).some(key => { const oldVal = appliedFilters[key]; const newVal = payload[key]; if (Array.isArray(oldVal) && Array.isArray(newVal)) { if (oldVal.length !== newVal.length) return true; const oldSet = new Set(oldVal); const newSet = new Set(newVal); for (const item of newSet) { if (!oldSet.has(item)) return true; } return false; } if ((oldVal === null && newVal === "") || (oldVal === "" && newVal === null)) return false; return oldVal !== newVal; }); if (areFiltersChanged) { setAppliedFilters(payload); resetGallery(); } }, [selectedFilters, appliedFilters]); const handleClearAllFilters = useCallback(() => { const initialStateSelected = { building: [], floor: [], activity: [], uploadedBy: [], workCategory: [], workArea: [], startDate: "", endDate: "", }; setSelectedFilters(initialStateSelected); const initialStateApplied = { buildingIds: null, floorIds: null, activityIds: null, uploadedByIds: null, workCategoryIds: null, workAreaIds: null, startDate: null, endDate: null, }; setAppliedFilters(initialStateApplied); resetGallery(); }, []); const scrollLeft = useCallback((key) => { imageGroupRefs.current[key]?.scrollBy({ left: -200, behavior: "smooth" }); }, []); const scrollRight = useCallback((key) => { imageGroupRefs.current[key]?.scrollBy({ left: 200, behavior: "smooth" }); }, []); const renderFilterCategory = (label, items, type) => (
toggleCollapse(type)}> {label}
{type === "dateRange" && (selectedFilters.startDate || selectedFilters.endDate) && ( )} {type !== "dateRange" && selectedFilters[type]?.length > 0 && ( )} {collapsedFilters[type] ? '+' : '-'}
{!collapsedFilters[type] && (
{type === "dateRange" ? (
) : ( items.map(([itemId, itemName]) => ( )) )}
)}
); return (
{loading && pageNumber === 1 ? (
) : images.length > 0 ? ( images.map((batch) => { const firstDoc = batch.documents[0]; const userName = `${firstDoc?.uploadedBy?.firstName || ""} ${firstDoc?.uploadedBy?.lastName || ""}`.trim(); const date = formatUTCToLocalTime(firstDoc?.uploadedAt); const showScrollButtons = batch.documents.length > SCROLL_THRESHOLD; return (
{userName} {date}
{batch.buildingName} > {batch.floorName} > {batch.workAreaName || "Unknown"} > {batch.activityName}
{batch.workCategoryName && (
{batch.workCategoryName}
)}
{showScrollButtons && }
(imageGroupRefs.current[batch.batchId] = el)}> {batch.documents.map((doc, idx) => { const hoverDate = moment(doc.uploadedAt).format("DD-MM-YYYY"); const hoverTime = moment(doc.uploadedAt).format("hh:mm A"); return (
openModal()} onMouseEnter={() => setHoveredImage(doc)} onMouseLeave={() => setHoveredImage(null)}>
{`Image
{hoveredImage === doc && (

Date: {hoverDate}

Time: {hoverTime}

Activity: {batch.activityName}

)}
); })}
{showScrollButtons && }
); }) ) : ( !loading &&

No images match the selected filters.

)}
{loadingMore && hasMore &&
} {!hasMore && !loading && images.length > 0 &&

You've reached the end of the images.

}
Filters
{renderFilterCategory("Date Range", [], "dateRange")} {renderFilterCategory("Building", buildings, "building")} {renderFilterCategory("Floor", floors, "floor")} {renderFilterCategory("Work Area", workAreas, "workArea")} {renderFilterCategory("Activity", activities, "activity")} {renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")} {renderFilterCategory("Work Category", workCategories, "workCategory")}
); }; export default ImageGallery;