From aebae577dfdf5c5bf799f19209b5c11230c9d4c4 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Sat, 5 Jul 2025 18:09:06 +0530 Subject: [PATCH] Changes in Image gallery and integrating singnalR. --- src/pages/Gallary/ImageGallary.jsx | 256 +++++++++++++++++--------- src/pages/Gallary/ImageGallery.css | 1 + src/pages/Gallary/ImageGalleryAPI.jsx | 8 +- src/pages/Gallary/ImagePop.jsx | 37 ++-- src/services/signalRService.js | 7 + 5 files changed, 204 insertions(+), 105 deletions(-) diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index bfa30e79..41e15348 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,15 @@ 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 ImageGallery = () => { const [images, setImages] = useState([]); + const [pageNumber, setPageNumber] = useState(1); + const [hasMore, setHasMore] = useState(true); const selectedProjectId = useSelector((store) => store.localVariables.projectId); const { openModal } = useModal(); @@ -51,10 +55,12 @@ 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 filterPanelRef = useRef(null); const filterButtonRef = useRef(null); + const loaderRef = useRef(null); useEffect(() => { const handleClickOutside = (event) => { @@ -67,7 +73,6 @@ const ImageGallery = () => { setIsFilterPanelOpen(false); } }; - document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); @@ -78,48 +83,120 @@ const ImageGallery = () => { if (!selectedProjectId) { setImages([]); 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); - }); + fetchImages(1, appliedFilters); }, [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]); - } + // ✅ 1. Define fetchImages first + 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 uniqueNew = newBatches.filter( + (batch) => !prevImages.some((prev) => prev.batchId === batch.batchId) + ); + return [...prevImages, ...uniqueNew]; }); - return Array.from(uniqueMap.entries()); - }, - [images] - ); + + setHasMore(receivedCount === PAGE_SIZE); + } catch (err) { + console.error("Error fetching images:", err); + setImages([]); + setHasMore(false); + } finally { + setLoading(false); + setLoadingMore(false); + } + }, [selectedProjectId]); + + // ✅ 2. THEN use fetchImages inside useEffect + useEffect(() => { + const handleExternalEvent = (data) => { + if (selectedProjectId == data.projectId) { + setImages([]); + setPageNumber(1); + setHasMore(true); + fetchImages(1, appliedFilters); // Now safe + } + }; + + 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, dataArray = images) => { + const map = new Map(); + dataArray.forEach(batch => { + const id = batch[idKey]; + const name = batch[nameKey]; + if (id && name && !map.has(id)) { + map.set(id, name); + } + }); + return Array.from(map.entries()); + }, [images]); 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); + images.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]); @@ -193,6 +270,9 @@ const ImageGallery = () => { }; setAppliedFilters(payload); setIsFilterPanelOpen(false); + setImages([]); + setPageNumber(1); + setHasMore(true); }, [selectedFilters]); const handleClearAllFilters = useCallback(() => { @@ -219,10 +299,15 @@ const ImageGallery = () => { endDate: null, }; setAppliedFilters(initialStateApplied); + setImages([]); + setPageNumber(1); + setHasMore(true); }, []); - const filteredImages = images.filter((img) => { - const uploadedAtMoment = moment(img.uploadedAt); + const filteredBatches = images.filter((batch) => { + const firstDocUploadedAt = batch.documents[0]?.uploadedAt; + const uploadedAtMoment = firstDocUploadedAt ? moment(firstDocUploadedAt) : null; + const startDateMoment = appliedFilters.startDate ? moment(appliedFilters.startDate) : null; @@ -231,35 +316,26 @@ const ImageGallery = () => { : null; const isWithinDateRange = - (!startDateMoment || uploadedAtMoment.isSameOrAfter(startDateMoment, "day")) && - (!endDateMoment || uploadedAtMoment.isSameOrBefore(endDateMoment, "day")); + (!startDateMoment || (uploadedAtMoment && uploadedAtMoment.isSameOrAfter(startDateMoment, "day"))) && + (!endDateMoment || (uploadedAtMoment && uploadedAtMoment.isSameOrBefore(endDateMoment, "day"))); const passesCategoryFilters = (appliedFilters.buildingIds === null || - appliedFilters.buildingIds.includes(img.buildingId)) && + appliedFilters.buildingIds.includes(batch.buildingId)) && (appliedFilters.floorIds === null || - appliedFilters.floorIds.includes(img.floorIds)) && + appliedFilters.floorIds.includes(batch.floorIds)) && (appliedFilters.activityIds === null || - appliedFilters.activityIds.includes(img.activityId)) && + appliedFilters.activityIds.includes(batch.activityId)) && (appliedFilters.workAreaIds === null || - appliedFilters.workAreaIds.includes(img.workAreaId)) && - (appliedFilters.uploadedByIds === null || - appliedFilters.uploadedByIds.includes(img.uploadedBy?.id)) && + appliedFilters.workAreaIds.includes(batch.workAreaId)) && (appliedFilters.workCategoryIds === null || - appliedFilters.workCategoryIds.includes(img.workCategoryId)); + appliedFilters.workCategoryIds.includes(batch.workCategoryId)); - return isWithinDateRange && passesCategoryFilters; - }); + const passesUploadedByFilter = + appliedFilters.uploadedByIds === null || + batch.documents.some(doc => appliedFilters.uploadedByIds.includes(doc.uploadedBy?.id)); - 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); + return isWithinDateRange && passesCategoryFilters && passesUploadedByFilter; }); const scrollLeft = useCallback((key) => { @@ -337,32 +413,32 @@ const ImageGallery = () => {
- {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"); + ) : filteredBatches.length > 0 ? ( + filteredBatches.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"); // Changed format to DD/MM/YYYY + const time = moment(firstDoc?.uploadedAt).format("hh:mm A"); return ( -
+
- {imgs[0].uploadedBy?.firstName}{" "} - {imgs[0].uploadedBy?.lastName} + {userName} {date} {time} @@ -373,13 +449,14 @@ const ImageGallery = () => {
- {buildingName} > {floorName} > {workArea} >{" "} - {activity} + {batch.buildingName} > {batch.floorName} >{" "} + {batch.workAreaName || "Unknown"} >{" "} + {batch.activityName}
- {workCategoryName && ( + {batch.workCategoryName && (
- {workCategoryName} + {batch.workCategoryName}
)} @@ -389,32 +466,32 @@ const ImageGallery = () => {
(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"); // Changed format to 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 +500,7 @@ const ImageGallery = () => { Time: {hoverTime}

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

)} @@ -433,7 +510,7 @@ const ImageGallery = () => {
@@ -442,10 +519,17 @@ const ImageGallery = () => { ); }) ) : ( -

+ !loading &&

No images match the selected filters.

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

You've reached the end of the images.

+ )} +
@@ -457,7 +541,7 @@ const ImageGallery = () => { > {isFilterPanelOpen ? ( <> - Filter {/* Added ms-1 for spacing */} + Filter ) : ( <> diff --git a/src/pages/Gallary/ImageGallery.css b/src/pages/Gallary/ImageGallery.css index 377fe531..25567dda 100644 --- a/src/pages/Gallary/ImageGallery.css +++ b/src/pages/Gallary/ImageGallery.css @@ -400,6 +400,7 @@ -webkit-overflow-scrolling: touch; scroll-behavior: smooth; width: 100%; + margin-left: 34px; } .scroll-arrow { diff --git a/src/pages/Gallary/ImageGalleryAPI.jsx b/src/pages/Gallary/ImageGalleryAPI.jsx index 13e6a56a..9ca09bfe 100644 --- a/src/pages/Gallary/ImageGalleryAPI.jsx +++ b/src/pages/Gallary/ImageGalleryAPI.jsx @@ -1,9 +1,9 @@ import { api } from "../../utils/axiosClient"; export const ImageGalleryAPI = { - - ImagesGet: (projectId, filter) => { + ImagesGet: (projectId, filter, pageNumber, pageSize) => { const payloadJsonString = JSON.stringify(filter); - return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}`) + // Corrected API endpoint with pagination parameters + return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}&pageNumber=${pageNumber}&pageSize=${pageSize}`); }, -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/pages/Gallary/ImagePop.jsx b/src/pages/Gallary/ImagePop.jsx index 62f4b8da..ec88e6d4 100644 --- a/src/pages/Gallary/ImagePop.jsx +++ b/src/pages/Gallary/ImagePop.jsx @@ -3,32 +3,38 @@ import "./ImagePop.css"; import { useModal } from "./ModalContext"; import moment from "moment"; -const ImagePop = ({ images, initialIndex = 0 }) => { +const ImagePop = ({ batch, initialIndex = 0 }) => { const { closeModal } = useModal(); - // State to keep track of the currently displayed image's index const [currentIndex, setCurrentIndex] = useState(initialIndex); - // Effect to update currentIndex if the initialIndex prop changes (e.g., if the modal is reused) + // Effect to update currentIndex if the initialIndex prop changes useEffect(() => { setCurrentIndex(initialIndex); - }, [initialIndex, images]); + }, [initialIndex, batch]); - // If no images are provided or the array is empty, don't render - if (!images || images.length === 0) return null; + // If no batch or documents are provided, don't render + if (!batch || !batch.documents || batch.documents.length === 0) return null; - // Get the current image based on currentIndex - const image = images[currentIndex]; + // Get the current image document from the batch's documents array + const image = batch.documents[currentIndex]; // Fallback if for some reason the image at the current index doesn't exist if (!image) return null; - // Format details for display + // Format details for display from the individual image document const fullName = `${image.uploadedBy?.firstName || ""} ${ image.uploadedBy?.lastName || "" }`.trim(); const date = moment(image.uploadedAt).format("YYYY-MM-DD"); const time = moment(image.uploadedAt).format("hh:mm A"); + // Location and category details from the 'batch' object (as previously corrected) + const buildingName = batch.buildingName; + const floorName = batch.floorName; + const workAreaName = batch.workAreaName; + const activityName = batch.activityName; + const batchComment = batch.comment; + // Handler for navigating to the previous image const handlePrev = () => { setCurrentIndex((prevIndex) => Math.max(0, prevIndex - 1)); @@ -37,13 +43,13 @@ const ImagePop = ({ images, initialIndex = 0 }) => { // Handler for navigating to the next image const handleNext = () => { setCurrentIndex((prevIndex) => - Math.min(images.length - 1, prevIndex + 1) + Math.min(batch.documents.length - 1, prevIndex + 1) ); }; // Determine if previous/next buttons should be enabled/visible const hasPrev = currentIndex > 0; - const hasNext = currentIndex < images.length - 1; + const hasNext = currentIndex < batch.documents.length - 1; return (
@@ -61,7 +67,7 @@ const ImagePop = ({ images, initialIndex = 0 }) => { )} {/* The main image display */} - Preview + Preview {/* Next button, only shown if there's a next image */} {hasNext && ( @@ -79,11 +85,12 @@ const ImagePop = ({ images, initialIndex = 0 }) => { 📅 Date: {date} {time}

- 🏢 Location: {image.buildingName} >{" "} - {image.floorName} > {image.activityName} + 🏢 Location: {buildingName} > {floorName} >{" "} + {workAreaName || "Unknown"} > {activityName}

- 📝 Comments: {image.comment} + {/* Display the comment from the batch object */} + 📝 Comments: {batchComment || "N/A"}

diff --git a/src/services/signalRService.js b/src/services/signalRService.js index 63cabc9c..92c09ec8 100644 --- a/src/services/signalRService.js +++ b/src/services/signalRService.js @@ -92,6 +92,13 @@ export function startSignalR(loggedUser) { clearCacheKey("AttendanceLogs") eventBus.emit("employee", data); } + + if (data.keyword == "Task_Report") { + eventBus.emit("image_gallery", data); + } + if (data.keyword == "Task_Comment") { + eventBus.emit("image_gallery", data); + } } });