import React, { useState, useEffect, useRef, useCallback } from "react"; import "./ImageGallery.css"; import { ImageGalleryAPI } from "./ImageGalleryAPI"; 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"; const PAGE_SIZE = 10; const SCROLL_THRESHOLD = 5; const ImageGallery = () => { const [images, setImages] = useState([]); const [allImagesData, setAllImagesData] = useState([]); const [pageNumber, setPageNumber] = useState(1); const [hasMore, setHasMore] = useState(true); const selectedProjectId = useSelector((store) => store.localVariables.projectId); 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 [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); 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) { setImages([]); setAllImagesData([]); setLoading(false); setHasMore(false); return; } setImages([]); setPageNumber(1); setHasMore(true); setLoading(true); setAllImagesData([]); fetchImages(1, appliedFilters, true); }, [selectedProjectId, appliedFilters]); const fetchImages = useCallback(async (page, filters) => { if (!selectedProjectId) return; try { if (page === 1) { setLoading(true); } else { setLoadingMore(true); } const res = await ImageGalleryAPI.ImagesGet(selectedProjectId, filters, page, PAGE_SIZE); const newBatches = res.data || []; const receivedCount = newBatches.length; setImages((prevImages) => { const uniqueNewBatches = newBatches.filter( (newBatch) => !prevImages.some((prevBatch) => prevBatch.batchId === newBatch.batchId) ); return [...prevImages, ...uniqueNewBatches]; }); setAllImagesData((prevAllImages) => { const uniqueAllImages = newBatches.filter( (newBatch) => !prevAllImages.some((prevBatch) => prevBatch.batchId === newBatch.batchId) ); return [...prevAllImages, ...uniqueAllImages]; }); setHasMore(receivedCount === PAGE_SIZE); } catch (err) { console.error("Error fetching images:", err); if (page === 1) { setImages([]); setAllImagesData([]); } setHasMore(false); } finally { setLoading(false); setLoadingMore(false); } }, [selectedProjectId]); useEffect(() => { const handleExternalEvent = (data) => { if (selectedProjectId === data.projectId) { setImages([]); setAllImagesData([]); setPageNumber(1); setHasMore(true); 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, fetchImages, appliedFilters]); const getUniqueValuesWithIds = useCallback((idKey, nameKey) => { const map = new Map(); allImagesData.forEach(batch => { let id; if (idKey === "floorIds") { id = batch.floorIds; } else { id = 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 > 0 ? selectedFilters.building.map((item) => item[0]) : null, floorIds: selectedFilters.floor.length > 0 ? selectedFilters.floor.map((item) => item[0]) : null, workAreaIds: selectedFilters.workArea.length > 0 ? selectedFilters.workArea.map((item) => item[0]) : null, workCategoryIds: selectedFilters.workCategory.length > 0 ? selectedFilters.workCategory.map((item) => item[0]) : null, activityIds: selectedFilters.activity.length > 0 ? selectedFilters.activity.map((item) => item[0]) : null, uploadedByIds: selectedFilters.uploadedBy.length > 0 ? 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); if (oldSet.size !== newSet.size) return true; 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); setImages([]); setPageNumber(1); setHasMore(true); } // Removed setIsFilterPanelOpen(false); to keep the drawer open }, [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); setImages([]); setPageNumber(1); setHasMore(true); }, []); 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) => (
Date: {hoverDate}
Time: {hoverTime}
Activity: {batch.activityName}
No images match the selected filters.
)}You've reached the end of the images.
)}