diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index bfa30e79..000ece10 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -1,4 +1,3 @@ -// ImageGallery.js import React, { useState, useEffect, useRef, useCallback } from "react"; import "./ImageGallery.css"; import { ImageGalleryAPI } from "./ImageGalleryAPI"; @@ -7,10 +6,17 @@ 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"; // Assuming this is the path to your DateRangePicker +import DateRangePicker from "../../components/common/DateRangePicker"; +import eventBus from "../../services/eventBus"; + +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(); @@ -51,8 +57,10 @@ const ImageGallery = () => { 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); @@ -68,61 +76,161 @@ const ImageGallery = () => { } }; - document.addEventListener("mousedown", handleClickOutside); + 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); - ImageGalleryAPI.ImagesGet(selectedProjectId, appliedFilters) - .then((res) => { - setImages(res.data); - }) - .catch((err) => { - console.error("Error fetching images:", err); - setImages([]); - }) - .finally(() => { - setLoading(false); - }); + setAllImagesData([]); + fetchImages(1, appliedFilters, true); }, [selectedProjectId, appliedFilters]); - const getUniqueValuesWithIds = useCallback( - (idKey, nameKey) => { - const uniqueMap = new Map(); - images.forEach((img) => { - if (img[idKey] && img[nameKey]) { - uniqueMap.set(img[idKey], img[nameKey]); - } + 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]; }); - return Array.from(uniqueMap.entries()); - }, - [images] - ); + + 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); + } + }); + // Sort alphabetically by name + return Array.from(map.entries()).sort((a, b) => a[1].localeCompare(b[1])); + }, [allImagesData]); const getUniqueUploadedByUsers = useCallback(() => { const uniqueUsersMap = new Map(); - images.forEach((img) => { - if (img.uploadedBy && img.uploadedBy.id) { - const fullName = `${img.uploadedBy.firstName || ""} ${ - img.uploadedBy.lastName || "" - }`.trim(); - if (fullName) { - uniqueUsersMap.set(img.uploadedBy.id, fullName); + 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()); - }, [images]); + // Sort alphabetically by full name + return Array.from(uniqueUsersMap.entries()).sort((a, b) => a[1].localeCompare(b[1])); + }, [allImagesData]); const buildings = getUniqueValuesWithIds("buildingId", "buildingName"); const floors = getUniqueValuesWithIds("floorIds", "floorName"); @@ -191,9 +299,35 @@ const ImageGallery = () => { startDate: selectedFilters.startDate || null, endDate: selectedFilters.endDate || null, }; - setAppliedFilters(payload); - setIsFilterPanelOpen(false); - }, [selectedFilters]); + + 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 = { @@ -219,49 +353,11 @@ const ImageGallery = () => { endDate: null, }; setAppliedFilters(initialStateApplied); + setImages([]); + setPageNumber(1); + setHasMore(true); }, []); - const filteredImages = images.filter((img) => { - const uploadedAtMoment = moment(img.uploadedAt); - const startDateMoment = appliedFilters.startDate - ? moment(appliedFilters.startDate) - : null; - const endDateMoment = appliedFilters.endDate - ? moment(appliedFilters.endDate) - : null; - - const isWithinDateRange = - (!startDateMoment || uploadedAtMoment.isSameOrAfter(startDateMoment, "day")) && - (!endDateMoment || uploadedAtMoment.isSameOrBefore(endDateMoment, "day")); - - const passesCategoryFilters = - (appliedFilters.buildingIds === null || - appliedFilters.buildingIds.includes(img.buildingId)) && - (appliedFilters.floorIds === null || - appliedFilters.floorIds.includes(img.floorIds)) && - (appliedFilters.activityIds === null || - appliedFilters.activityIds.includes(img.activityId)) && - (appliedFilters.workAreaIds === null || - appliedFilters.workAreaIds.includes(img.workAreaId)) && - (appliedFilters.uploadedByIds === null || - appliedFilters.uploadedByIds.includes(img.uploadedBy?.id)) && - (appliedFilters.workCategoryIds === null || - appliedFilters.workCategoryIds.includes(img.workCategoryId)); - - return isWithinDateRange && passesCategoryFilters; - }); - - const imagesByActivityUser = {}; - filteredImages.forEach((img) => { - const userName = `${img.uploadedBy?.firstName || ""} ${ - img.uploadedBy?.lastName || "" - }`.trim(); - const workArea = img.workAreaName || "Unknown"; - const key = `${img.activityName}__${userName}__${workArea}`; - if (!imagesByActivityUser[key]) imagesByActivityUser[key] = []; - imagesByActivityUser[key].push(img); - }); - const scrollLeft = useCallback((key) => { imageGroupRefs.current[key]?.scrollBy({ left: -200, behavior: "smooth" }); }, []); @@ -307,7 +403,15 @@ const ImageGallery = () => { {!collapsedFilters[type] && (
{type === "dateRange" ? ( - null +
+ +
) : ( items.map((item) => { const itemId = item[0]; @@ -334,35 +438,49 @@ const ImageGallery = () => { ); return ( -
+
+ +
- {loading ? ( + {loading && pageNumber === 1 ? (
- ) : Object.entries(imagesByActivityUser).length > 0 ? ( - Object.entries(imagesByActivityUser).map(([key, imgs]) => { - const [activity, userName, workArea] = key.split("__"); - const { buildingName, floorName, uploadedAt, workCategoryName } = imgs[0]; - const date = moment(uploadedAt).format("YYYY-MM-DD"); - const time = moment(uploadedAt).format("hh:mm A"); + ) : images.length > 0 ? ( + images.map((batch) => { + const firstDoc = batch.documents[0]; + const userName = `${firstDoc?.uploadedBy?.firstName || ""} ${firstDoc?.uploadedBy?.lastName || "" + }`.trim(); + const date = moment(firstDoc?.uploadedAt).format("DD-MM-YYYY"); + const time = moment(firstDoc?.uploadedAt).format("hh:mm A"); + + const showScrollButtons = batch.documents.length > SCROLL_THRESHOLD; return ( -
+
- {imgs[0].uploadedBy?.firstName}{" "} - {imgs[0].uploadedBy?.lastName} + {userName} {date} {time} @@ -373,13 +491,14 @@ const ImageGallery = () => {
- {buildingName} > {floorName} > {workArea} >{" "} - {activity} + {batch.buildingName} > {batch.floorName} >{" "} + {batch.workAreaName || "Unknown"} >{" "} + {batch.activityName}
- {workCategoryName && ( + {batch.workCategoryName && (
- {workCategoryName} + {batch.workCategoryName}
)} @@ -387,34 +506,36 @@ const ImageGallery = () => {
- + {showScrollButtons && ( + + )}
(imageGroupRefs.current[key] = el)} + ref={(el) => (imageGroupRefs.current[batch.batchId] = el)} > - {imgs.map((img, idx) => { - const hoverDate = moment(img.uploadedAt).format("YYYY-MM-DD"); - const hoverTime = moment(img.uploadedAt).format("hh:mm A"); + {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() + openModal() } - onMouseEnter={() => setHoveredImage(img)} + onMouseEnter={() => setHoveredImage(doc)} onMouseLeave={() => setHoveredImage(null)} >
- {`Image + {`Image
- {hoveredImage === img && ( + {hoveredImage === doc && (

Date: {hoverDate} @@ -423,7 +544,7 @@ const ImageGallery = () => { Time: {hoverTime}

- Activity: {img.activityName} + Activity: {batch.activityName}

)} @@ -431,61 +552,62 @@ const ImageGallery = () => { ); })}
- + {showScrollButtons && ( + + )}
); }) ) : ( -

+ !loading &&

No images match the selected filters.

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

You've reached the end of the images.

+ )} +
-
- -
-
-
toggleCollapse('dateRange')}> - Date Range - - - {collapsedFilters.dateRange ? '+' : '-'} - -
- {!collapsedFilters.dateRange && ( -
- -
- )} -
+
+
+
+ Filters +
+ +
+ {/* Top Filter Actions */} +
+ + +
+
+ {renderFilterCategory("Date Range", [], "dateRange")} {renderFilterCategory("Building", buildings, "building")} {renderFilterCategory("Floor", floors, "floor")} {renderFilterCategory("Work Area", workAreas, "workArea")} @@ -493,14 +615,14 @@ const ImageGallery = () => { {renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")} {renderFilterCategory("Work Category", workCategories, "workCategory")} -
+ {/*
-
+
*/}
diff --git a/src/pages/Gallary/ImageGallery.css b/src/pages/Gallary/ImageGallery.css index 377fe531..f0bb9f9a 100644 --- a/src/pages/Gallary/ImageGallery.css +++ b/src/pages/Gallary/ImageGallery.css @@ -1,7 +1,5 @@ -/* ImageGallery.css */ .gallery-container { display: grid; - grid-template-columns: 1fr 50px; gap: 4px; padding: 25px; font-family: sans-serif; @@ -69,7 +67,7 @@ transition: background-color 0.2s ease, box-shadow 0.2s ease, width 0.3s ease-in-out, height 0.3s ease-in-out, border-radius 0.3s ease-in-out, padding 0.3s ease-in-out; position: absolute; - top: 0; + top: 250px; right: 0; height: 40px; width: 40px; @@ -140,7 +138,6 @@ border-radius: 0 0 4px 4px; max-height: 150px; /* Default max-height for scrollable dropdowns */ - overflow-y: auto; /* Default overflow for scrollable dropdowns */ transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; } @@ -246,6 +243,7 @@ z-index: 1; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: background-color 0.3s ease, border-bottom 0.3s ease; + margin-top: 5px; } .dropdown.collapsed .dropdown-header { @@ -400,6 +398,7 @@ -webkit-overflow-scrolling: touch; scroll-behavior: smooth; width: 100%; + margin-left: 34px; } .scroll-arrow { @@ -543,4 +542,8 @@ hr { .datepicker { margin-right: 135px; margin-top: 6px; +} +.px-3 { + padding-inline-end: 1.75rem !important; + padding-inline-start: 14.25rem !important; } \ No newline at end of file