From aebae577dfdf5c5bf799f19209b5c11230c9d4c4 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Sat, 5 Jul 2025 18:09:06 +0530 Subject: [PATCH 01/10] 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); + } } }); From 4adfbc8e8eb6db44b33e328449417f8fe76818ca Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Sat, 5 Jul 2025 21:59:18 +0530 Subject: [PATCH 02/10] Adding next and previous button hide and correction in filter logic. --- src/pages/Gallary/ImageGallary.jsx | 130 +++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 37 deletions(-) diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index 41e15348..e12ad6c7 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -10,6 +10,7 @@ import DateRangePicker from "../../components/common/DateRangePicker"; import eventBus from "../../services/eventBus"; const PAGE_SIZE = 10; +const SCROLL_THRESHOLD = 5; // Define how many images are visible before scroll buttons appear const ImageGallery = () => { const [images, setImages] = useState([]); @@ -17,6 +18,7 @@ const ImageGallery = () => { const [hasMore, setHasMore] = useState(true); const selectedProjectId = useSelector((store) => store.localVariables.projectId); const { openModal } = useModal(); + const [initialImageCache, setInitialImageCache] = useState([]); const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); @@ -95,23 +97,41 @@ const ImageGallery = () => { fetchImages(1, appliedFilters); }, [selectedProjectId, appliedFilters]); - // ✅ 1. Define fetchImages first const fetchImages = useCallback(async (page, filters) => { if (!selectedProjectId) return; try { - if (page === 1) setLoading(true); - else setLoadingMore(true); + if (page === 1) { + setLoading(true); + const noFiltersApplied = Object.values(filters).every( + (val) => val === null || (Array.isArray(val) && val.length === 0) + ); + + if (noFiltersApplied) { + setInitialImageCache([]); // Reset cache before fetching + } + } 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) + const uniqueNewBatches = newBatches.filter( + (newBatch) => !prevImages.some((prevBatch) => prevBatch.batchId === newBatch.batchId) ); - return [...prevImages, ...uniqueNew]; + const updatedImages = [...prevImages, ...uniqueNewBatches]; + + const noFiltersApplied = Object.values(filters).every( + (val) => val === null || (Array.isArray(val) && val.length === 0) + ); + if (page === 1 && noFiltersApplied) { + setInitialImageCache(updatedImages); + } + + return updatedImages; }); setHasMore(receivedCount === PAGE_SIZE); @@ -125,14 +145,19 @@ const ImageGallery = () => { } }, [selectedProjectId]); - // ✅ 2. THEN use fetchImages inside useEffect + const isNoFiltersApplied = (filters) => + Object.values(filters).every( + (val) => val === null || (Array.isArray(val) && val.length === 0) + ); + + useEffect(() => { const handleExternalEvent = (data) => { if (selectedProjectId == data.projectId) { setImages([]); setPageNumber(1); setHasMore(true); - fetchImages(1, appliedFilters); // Now safe + fetchImages(1, appliedFilters); } }; @@ -141,7 +166,7 @@ const ImageGallery = () => { return () => { eventBus.off("image_gallery", handleExternalEvent); }; - }, [appliedFilters, fetchImages,selectedProjectId]); + }, [appliedFilters, fetchImages, selectedProjectId]); useEffect(() => { if (!loaderRef.current) return; @@ -174,7 +199,7 @@ const ImageGallery = () => { } }, [pageNumber, fetchImages, appliedFilters]); - const getUniqueValuesWithIds = useCallback((idKey, nameKey, dataArray = images) => { + const getUniqueValuesWithIds = useCallback((idKey, nameKey, dataArray = initialImageCache) => { const map = new Map(); dataArray.forEach(batch => { const id = batch[idKey]; @@ -184,7 +209,8 @@ const ImageGallery = () => { } }); return Array.from(map.entries()); - }, [images]); + }, [initialImageCache]); + const getUniqueUploadedByUsers = useCallback(() => { const uniqueUsersMap = new Map(); @@ -201,12 +227,13 @@ const ImageGallery = () => { return Array.from(uniqueUsersMap.entries()); }, [images]); - const buildings = getUniqueValuesWithIds("buildingId", "buildingName"); - const floors = getUniqueValuesWithIds("floorIds", "floorName"); - const activities = getUniqueValuesWithIds("activityId", "activityName"); - const workAreas = getUniqueValuesWithIds("workAreaId", "workAreaName"); + const buildings = getUniqueValuesWithIds("buildingId", "buildingName", initialImageCache); + + const floors = getUniqueValuesWithIds("floorId", "floorName", initialImageCache); + const activities = getUniqueValuesWithIds("activityId", "activityName", initialImageCache); + const workAreas = getUniqueValuesWithIds("workAreaId", "workAreaName", initialImageCache); const uploadedByUsers = getUniqueUploadedByUsers(); - const workCategories = getUniqueValuesWithIds("workCategoryId", "workCategoryName"); + const workCategories = getUniqueValuesWithIds("workCategoryId", "workCategoryName", initialImageCache); const toggleFilter = useCallback((type, itemId, itemName) => { setSelectedFilters((prev) => { @@ -268,12 +295,32 @@ const ImageGallery = () => { startDate: selectedFilters.startDate || null, endDate: selectedFilters.endDate || null, }; - setAppliedFilters(payload); - setIsFilterPanelOpen(false); - setImages([]); - setPageNumber(1); - setHasMore(true); - }, [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; + } + return oldVal !== newVal; + }); + + if (areFiltersChanged) { + setAppliedFilters(payload); + setImages([]); + setPageNumber(1); + setHasMore(true); + } + }, [selectedFilters, appliedFilters]); + const handleClearAllFilters = useCallback(() => { const initialStateSelected = { @@ -323,7 +370,7 @@ const ImageGallery = () => { (appliedFilters.buildingIds === null || appliedFilters.buildingIds.includes(batch.buildingId)) && (appliedFilters.floorIds === null || - appliedFilters.floorIds.includes(batch.floorIds)) && + appliedFilters.floorIds.includes(batch.floorId)) && (appliedFilters.activityIds === null || appliedFilters.activityIds.includes(batch.activityId)) && (appliedFilters.workAreaIds === null || @@ -422,9 +469,12 @@ const ImageGallery = () => { 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 date = moment(firstDoc?.uploadedAt).format("DD-MM-YYYY"); const time = moment(firstDoc?.uploadedAt).format("hh:mm A"); + // Determine if scroll buttons should be shown for this batch + const showScrollButtons = batch.documents.length > SCROLL_THRESHOLD; + return (
@@ -464,18 +514,21 @@ const ImageGallery = () => {
- + {/* Render Left Scroll Button conditionally */} + {showScrollButtons && ( + + )}
(imageGroupRefs.current[batch.batchId] = el)} > {batch.documents.map((doc, idx) => { - const hoverDate = moment(doc.uploadedAt).format("DD-MM-YYYY"); // Changed format to DD/MM/YYYY + const hoverDate = moment(doc.uploadedAt).format("DD-MM-YYYY"); const hoverTime = moment(doc.uploadedAt).format("hh:mm A"); return ( @@ -508,12 +561,15 @@ const ImageGallery = () => { ); })}
- + {/* Render Right Scroll Button conditionally */} + {showScrollButtons && ( + + )}
); From 9eb8418330e4c27a056962ec4c56807b712488a4 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Sun, 6 Jul 2025 13:37:42 +0530 Subject: [PATCH 03/10] Change the design of Filter drower. --- src/pages/Gallary/ImageGallary.jsx | 227 +++++++++++++---------------- src/pages/Gallary/ImageGallery.css | 6 +- 2 files changed, 101 insertions(+), 132 deletions(-) diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index e12ad6c7..70f3cf9c 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -10,15 +10,15 @@ import DateRangePicker from "../../components/common/DateRangePicker"; import eventBus from "../../services/eventBus"; const PAGE_SIZE = 10; -const SCROLL_THRESHOLD = 5; // Define how many images are visible before scroll buttons appear +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 [initialImageCache, setInitialImageCache] = useState([]); const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); @@ -60,9 +60,9 @@ const ImageGallery = () => { const [loadingMore, setLoadingMore] = useState(false); const imageGroupRefs = useRef({}); + const loaderRef = useRef(null); const filterPanelRef = useRef(null); const filterButtonRef = useRef(null); - const loaderRef = useRef(null); useEffect(() => { const handleClickOutside = (event) => { @@ -75,15 +75,22 @@ const ImageGallery = () => { setIsFilterPanelOpen(false); } }; - 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; @@ -94,7 +101,8 @@ const ImageGallery = () => { setHasMore(true); setLoading(true); - fetchImages(1, appliedFilters); + setAllImagesData([]); + fetchImages(1, appliedFilters, true); }, [selectedProjectId, appliedFilters]); const fetchImages = useCallback(async (page, filters) => { @@ -103,13 +111,6 @@ const ImageGallery = () => { try { if (page === 1) { setLoading(true); - const noFiltersApplied = Object.values(filters).every( - (val) => val === null || (Array.isArray(val) && val.length === 0) - ); - - if (noFiltersApplied) { - setInitialImageCache([]); // Reset cache before fetching - } } else { setLoadingMore(true); } @@ -122,22 +123,23 @@ const ImageGallery = () => { const uniqueNewBatches = newBatches.filter( (newBatch) => !prevImages.some((prevBatch) => prevBatch.batchId === newBatch.batchId) ); - const updatedImages = [...prevImages, ...uniqueNewBatches]; + return [...prevImages, ...uniqueNewBatches]; + }); - const noFiltersApplied = Object.values(filters).every( - (val) => val === null || (Array.isArray(val) && val.length === 0) + setAllImagesData((prevAllImages) => { + const uniqueAllImages = newBatches.filter( + (newBatch) => !prevAllImages.some((prevBatch) => prevBatch.batchId === newBatch.batchId) ); - if (page === 1 && noFiltersApplied) { - setInitialImageCache(updatedImages); - } - - return updatedImages; + return [...prevAllImages, ...uniqueAllImages]; }); setHasMore(receivedCount === PAGE_SIZE); } catch (err) { console.error("Error fetching images:", err); - setImages([]); + if (page === 1) { + setImages([]); + setAllImagesData([]); + } setHasMore(false); } finally { setLoading(false); @@ -145,19 +147,14 @@ const ImageGallery = () => { } }, [selectedProjectId]); - const isNoFiltersApplied = (filters) => - Object.values(filters).every( - (val) => val === null || (Array.isArray(val) && val.length === 0) - ); - - useEffect(() => { const handleExternalEvent = (data) => { - if (selectedProjectId == data.projectId) { + if (selectedProjectId === data.projectId) { setImages([]); + setAllImagesData([]); setPageNumber(1); setHasMore(true); - fetchImages(1, appliedFilters); + fetchImages(1, appliedFilters, true); } }; @@ -199,22 +196,28 @@ const ImageGallery = () => { } }, [pageNumber, fetchImages, appliedFilters]); - const getUniqueValuesWithIds = useCallback((idKey, nameKey, dataArray = initialImageCache) => { + const getUniqueValuesWithIds = useCallback((idKey, nameKey) => { const map = new Map(); - dataArray.forEach(batch => { - const id = batch[idKey]; + 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()); - }, [initialImageCache]); - + }, [allImagesData]); const getUniqueUploadedByUsers = useCallback(() => { const uniqueUsersMap = new Map(); - images.forEach(batch => { + allImagesData.forEach(batch => { batch.documents.forEach(doc => { if (doc.uploadedBy && doc.uploadedBy.id) { const fullName = `${doc.uploadedBy.firstName || ""} ${doc.uploadedBy.lastName || ""}`.trim(); @@ -225,15 +228,14 @@ const ImageGallery = () => { }); }); return Array.from(uniqueUsersMap.entries()); - }, [images]); + }, [allImagesData]); - const buildings = getUniqueValuesWithIds("buildingId", "buildingName", initialImageCache); - - const floors = getUniqueValuesWithIds("floorId", "floorName", initialImageCache); - const activities = getUniqueValuesWithIds("activityId", "activityName", initialImageCache); - const workAreas = getUniqueValuesWithIds("workAreaId", "workAreaName", initialImageCache); + 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", initialImageCache); + const workCategories = getUniqueValuesWithIds("workCategoryId", "workCategoryName"); const toggleFilter = useCallback((type, itemId, itemName) => { setSelectedFilters((prev) => { @@ -310,18 +312,21 @@ const ImageGallery = () => { } return false; } + if ((oldVal === null && newVal === "") || (oldVal === "" && newVal === null)) { + return false; + } return oldVal !== newVal; }); if (areFiltersChanged) { - setAppliedFilters(payload); - setImages([]); - setPageNumber(1); - setHasMore(true); + setAppliedFilters(payload); + setImages([]); + setPageNumber(1); + setHasMore(true); } + // Removed setIsFilterPanelOpen(false); to keep the drawer open }, [selectedFilters, appliedFilters]); - const handleClearAllFilters = useCallback(() => { const initialStateSelected = { building: [], @@ -351,40 +356,6 @@ const ImageGallery = () => { setHasMore(true); }, []); - 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; - const endDateMoment = appliedFilters.endDate - ? moment(appliedFilters.endDate) - : null; - - const isWithinDateRange = - (!startDateMoment || (uploadedAtMoment && uploadedAtMoment.isSameOrAfter(startDateMoment, "day"))) && - (!endDateMoment || (uploadedAtMoment && uploadedAtMoment.isSameOrBefore(endDateMoment, "day"))); - - const passesCategoryFilters = - (appliedFilters.buildingIds === null || - appliedFilters.buildingIds.includes(batch.buildingId)) && - (appliedFilters.floorIds === null || - appliedFilters.floorIds.includes(batch.floorId)) && - (appliedFilters.activityIds === null || - appliedFilters.activityIds.includes(batch.activityId)) && - (appliedFilters.workAreaIds === null || - appliedFilters.workAreaIds.includes(batch.workAreaId)) && - (appliedFilters.workCategoryIds === null || - appliedFilters.workCategoryIds.includes(batch.workCategoryId)); - - const passesUploadedByFilter = - appliedFilters.uploadedByIds === null || - batch.documents.some(doc => appliedFilters.uploadedByIds.includes(doc.uploadedBy?.id)); - - return isWithinDateRange && passesCategoryFilters && passesUploadedByFilter; - }); - const scrollLeft = useCallback((key) => { imageGroupRefs.current[key]?.scrollBy({ left: -200, behavior: "smooth" }); }, []); @@ -430,7 +401,15 @@ const ImageGallery = () => { {!collapsedFilters[type] && (
{type === "dateRange" ? ( - null +
+ +
) : ( items.map((item) => { const itemId = item[0]; @@ -457,22 +436,33 @@ const ImageGallery = () => { ); return ( -
+
+ +
{loading && pageNumber === 1 ? (
- ) : filteredBatches.length > 0 ? ( - filteredBatches.map((batch) => { + ) : 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"); - // Determine if scroll buttons should be shown for this batch const showScrollButtons = batch.documents.length > SCROLL_THRESHOLD; return ( @@ -514,7 +504,6 @@ const ImageGallery = () => {
- {/* Render Left Scroll Button conditionally */} {showScrollButtons && (
- {/* Render Right Scroll Button conditionally */} {showScrollButtons && (
-
- -
-
-
toggleCollapse('dateRange')}> - Date Range - - - {collapsedFilters.dateRange ? '+' : '-'} - -
- {!collapsedFilters.dateRange && ( -
- -
- )} -
+
+
+
+ Filters +
+ +
+
+ {renderFilterCategory("Date Range", [], "dateRange")} {renderFilterCategory("Building", buildings, "building")} {renderFilterCategory("Floor", floors, "floor")} {renderFilterCategory("Work Area", workAreas, "workArea")} @@ -633,7 +604,7 @@ 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 25567dda..4f7ada69 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; @@ -544,4 +542,4 @@ hr { .datepicker { margin-right: 135px; margin-top: 6px; -} \ No newline at end of file +} From c88af2441fa1c4ff0bead4afb68201ea4d85108e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 11:25:41 +0530 Subject: [PATCH 04/10] Added condition to check if number of images uploaded is greater than 0 --- src/services/signalRService.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/signalRService.js b/src/services/signalRService.js index 92c09ec8..563fc468 100644 --- a/src/services/signalRService.js +++ b/src/services/signalRService.js @@ -94,10 +94,14 @@ export function startSignalR(loggedUser) { } if (data.keyword == "Task_Report") { + if(data.numberOfImages > 0){ eventBus.emit("image_gallery", data); + } } if (data.keyword == "Task_Comment") { + if(data.numberOfImages > 0){ eventBus.emit("image_gallery", data); + } } } }); From 04ffa0f645e983ddef81f87c17084340ec3a0f5d Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Mon, 7 Jul 2025 12:42:19 +0530 Subject: [PATCH 05/10] fixed filter button of image galllary --- src/pages/Gallary/ImageGallary.jsx | 23 +++++++++++---------- src/pages/Gallary/ImageGallery.css | 7 +++---- src/utils/dateUtils.jsx | 32 ++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index 70f3cf9c..54573c56 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -212,7 +212,7 @@ const ImageGallery = () => { map.set(id, name); } }); - return Array.from(map.entries()); + return Array.from(map.entries()).sort((a, b) => a[1].localeCompare(b[1])); }, [allImagesData]); const getUniqueUploadedByUsers = useCallback(() => { @@ -227,7 +227,7 @@ const ImageGallery = () => { } }); }); - return Array.from(uniqueUsersMap.entries()); + return Array.from(uniqueUsersMap.entries()).sort((a, b) => a[1].localeCompare(b[1])); }, [allImagesData]); const buildings = getUniqueValuesWithIds("buildingId", "buildingName"); @@ -365,7 +365,7 @@ const ImageGallery = () => { }, []); const renderFilterCategory = (label, items, type) => ( -
+
toggleCollapse(type)}> {label}
@@ -595,6 +595,14 @@ const ImageGallery = () => { aria-label="Close" >
+
+ + +
{renderFilterCategory("Date Range", [], "dateRange")} {renderFilterCategory("Building", buildings, "building")} @@ -604,14 +612,7 @@ 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 4f7ada69..3724e0f6 100644 --- a/src/pages/Gallary/ImageGallery.css +++ b/src/pages/Gallary/ImageGallery.css @@ -3,9 +3,7 @@ gap: 4px; padding: 25px; font-family: sans-serif; - height: calc(100vh - 20px); box-sizing: border-box; - background-color: #f7f9fc; transition: grid-template-columns 0.3s ease-in-out; } @@ -67,8 +65,9 @@ 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: 250px; + top: 150px; right: 0; + position: fixed; height: 40px; width: 40px; z-index: 100; @@ -138,7 +137,7 @@ 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; } diff --git a/src/utils/dateUtils.jsx b/src/utils/dateUtils.jsx index cb393e41..fbe47e74 100644 --- a/src/utils/dateUtils.jsx +++ b/src/utils/dateUtils.jsx @@ -20,12 +20,12 @@ export const getDateDifferenceInDays = (startDate, endDate) => { return differenceInDays; }; -export const formatDate = (date) => { - if (!date) return ""; // Return an empty string if no date - const dateObj = new Date(date); - // return dateObj.toISOString().split("T")[0]; - return dateObj.toLocaleDateString('en-CA'); // Get the date in YYYY-MM-DD format -}; +// export const formatDate = (date) => { +// if (!date) return ""; // Return an empty string if no date +// const dateObj = new Date(date); +// // return dateObj.toISOString().split("T")[0]; +// return dateObj.toLocaleDateString('en-CA'); // Get the date in YYYY-MM-DD format +// }; export const convertShortTime = (dateString) => { const date = new Date(dateString); @@ -61,3 +61,23 @@ export const checkIfCurrentDate = (dateString) => { return currentDate.getTime() === inputDate.getTime(); }; +function utcToLocalTime(utcTime) { + // Convert string to Date if needed + const utcDate = typeof utcTime === 'string' ? new Date(utcTime) : utcTime; + + return new Date(utcDate.getTime()); +} + +export const formatDate =(utcTime, options = {}) =>{ + const localDate = utcToLocalTime(utcTime); + + const defaultOptions = { + day: '2-digit', + month: '2-digit', + year: 'numeric', + ...options + }; + + return localDate.toLocaleDateString('en-GB', defaultOptions); +} + From 287d11660965e49672874928b9be8308f0d1af78 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Mon, 7 Jul 2025 12:52:43 +0530 Subject: [PATCH 06/10] added cosmetic changes --- src/components/Project/AboutProject.jsx | 4 ++-- src/data/menuData.json | 2 +- src/pages/Gallary/ImageGallary.jsx | 2 +- src/pages/Gallary/ImageGallery.css | 3 --- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/Project/AboutProject.jsx b/src/components/Project/AboutProject.jsx index a64a09d1..bf07c51e 100644 --- a/src/components/Project/AboutProject.jsx +++ b/src/components/Project/AboutProject.jsx @@ -71,8 +71,8 @@ const AboutProject = ({ data }) => {
{data && (
-
-
+
+
{" "} Project Profile diff --git a/src/data/menuData.json b/src/data/menuData.json index cadf34bf..bbd37e2c 100644 --- a/src/data/menuData.json +++ b/src/data/menuData.json @@ -50,7 +50,7 @@ { "text": "Daily Expenses", "available": true, - "link": "/activities/gallary" + "link": "/activities/reports" } ] }, diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index 54573c56..1cd42476 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -366,7 +366,7 @@ const ImageGallery = () => { const renderFilterCategory = (label, items, type) => (
-
toggleCollapse(type)}> +
toggleCollapse(type)}> {label}
{type === "dateRange" && (selectedFilters.startDate || selectedFilters.endDate) && ( diff --git a/src/pages/Gallary/ImageGallery.css b/src/pages/Gallary/ImageGallery.css index 3724e0f6..eb9b4f7c 100644 --- a/src/pages/Gallary/ImageGallery.css +++ b/src/pages/Gallary/ImageGallery.css @@ -194,9 +194,6 @@ transition: background 0.2s; } -.dropdown-content label:hover { - background-color: #eef2ff; -} .dropdown-content input[type="checkbox"] { -webkit-appearance: none; From 76aa9200f77e50e242a490ed2bee3ed16a0dc1b0 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Mon, 7 Jul 2025 13:25:44 +0530 Subject: [PATCH 07/10] added Breadcrumb for image gallary --- src/pages/Gallary/ImageGallary.jsx | 9 ++++++++- src/pages/Gallary/ImageGallery.css | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index 1cd42476..90865a9b 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -8,6 +8,7 @@ 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"; const PAGE_SIZE = 10; const SCROLL_THRESHOLD = 5; @@ -436,7 +437,13 @@ const ImageGallery = () => { ); return ( -
+
+
diff --git a/src/pages/Gallary/ImagePop.jsx b/src/pages/Gallary/ImagePop.jsx index ec88e6d4..86cad334 100644 --- a/src/pages/Gallary/ImagePop.jsx +++ b/src/pages/Gallary/ImagePop.jsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react"; import "./ImagePop.css"; import { useModal } from "./ModalContext"; import moment from "moment"; +import {formatUTCToLocalTime} from "../../utils/dateUtils"; const ImagePop = ({ batch, initialIndex = 0 }) => { const { closeModal } = useModal(); @@ -25,9 +26,8 @@ const ImagePop = ({ batch, initialIndex = 0 }) => { 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"); - + const date = formatUTCToLocalTime(image.uploadedAt); + // Location and category details from the 'batch' object (as previously corrected) const buildingName = batch.buildingName; const floorName = batch.floorName; @@ -82,7 +82,7 @@ const ImagePop = ({ batch, initialIndex = 0 }) => { 👤 Uploaded By: {fullName}

- 📅 Date: {date} {time} + 📅 Date: {date}

🏢 Location: {buildingName} > {floorName} >{" "}