diff --git a/src/hooks/useImageGallery.js b/src/hooks/useImageGallery.js new file mode 100644 index 00000000..7e8e5334 --- /dev/null +++ b/src/hooks/useImageGallery.js @@ -0,0 +1,85 @@ +import { useState, useCallback } from "react"; +// import { ImageGalleryAPI } from "../repositories/ImageGalleyRepository"; +import { ImageGalleryAPI } from "../repositories/ImageGalleryAPI"; + +const PAGE_SIZE = 10; + +const useImageGallery = (selectedProjectId) => { + const [images, setImages] = useState([]); + const [allImagesData, setAllImagesData] = useState([]); + const [pageNumber, setPageNumber] = useState(1); + const [hasMore, setHasMore] = useState(true); + const [loading, setLoading] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + + const fetchImages = useCallback(async (page = 1, filters = {}, reset = false) => { + 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((prev) => { + if (page === 1 || reset) return newBatches; + const uniqueNew = newBatches.filter( + (batch) => !prev.some((b) => b.batchId === batch.batchId) + ); + return [...prev, ...uniqueNew]; + }); + + setAllImagesData((prev) => { + if (page === 1 || reset) return newBatches; + const uniqueAll = newBatches.filter( + (batch) => !prev.some((b) => b.batchId === batch.batchId) + ); + return [...prev, ...uniqueAll]; + }); + + setHasMore(receivedCount === PAGE_SIZE); + } catch (error) { + console.error("Error fetching images:", error); + if (page === 1) { + setImages([]); + setAllImagesData([]); + } + setHasMore(false); + } finally { + setLoading(false); + setLoadingMore(false); + } + }, [selectedProjectId]); + + const resetGallery = useCallback(() => { + setImages([]); + setAllImagesData([]); + setPageNumber(1); + setHasMore(true); + }, []); + + return { + images, + allImagesData, + pageNumber, + setPageNumber, + hasMore, + loading, + loadingMore, + fetchImages, + resetGallery, + }; +}; + +export default useImageGallery; \ No newline at end of file diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index 172d9d8c..eb7bd8ad 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -1,25 +1,32 @@ 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"; -import DateRangePicker from "../../components/common/DateRangePicker"; +import useImageGallery from "../../hooks/useImageGallery"; -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 { + images, + allImagesData, + pageNumber, + setPageNumber, + hasMore, + loading, + loadingMore, + fetchImages, + resetGallery, + } = useImageGallery(selectedProjectId); + const { openModal } = useModal(); const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); @@ -58,8 +65,6 @@ 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); @@ -91,71 +96,18 @@ const ImageGallery = () => { useEffect(() => { if (!selectedProjectId) { - setImages([]); - setAllImagesData([]); - setLoading(false); - setHasMore(false); + resetGallery(); return; } - setImages([]); - setPageNumber(1); - setHasMore(true); - setLoading(true); - - setAllImagesData([]); + resetGallery(); 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); + resetGallery(); fetchImages(1, appliedFilters, true); } }; @@ -196,20 +148,13 @@ const ImageGallery = () => { if (pageNumber > 1) { fetchImages(pageNumber, appliedFilters); } - }, [pageNumber, fetchImages, appliedFilters]); + }, [pageNumber]); const getUniqueValuesWithIds = useCallback((idKey, nameKey) => { const map = new Map(); allImagesData.forEach(batch => { - let id; - if (idKey === "floorIds") { - id = batch.floorIds; - } else { - id = batch[idKey]; - } - + let id = idKey === "floorIds" ? batch.floorIds : batch[idKey]; const name = batch[nameKey]; - if (id && name && !map.has(id)) { map.set(id, name); } @@ -272,30 +217,12 @@ const ImageGallery = () => { 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, + 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, }; @@ -303,30 +230,23 @@ const ImageGallery = () => { 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; - } + if ((oldVal === null && newVal === "") || (oldVal === "" && newVal === null)) return false; return oldVal !== newVal; }); if (areFiltersChanged) { setAppliedFilters(payload); - setImages([]); - setPageNumber(1); - setHasMore(true); + resetGallery(); } - // Removed setIsFilterPanelOpen(false); to keep the drawer open }, [selectedFilters, appliedFilters]); const handleClearAllFilters = useCallback(() => { @@ -353,9 +273,7 @@ const ImageGallery = () => { endDate: null, }; setAppliedFilters(initialStateApplied); - setImages([]); - setPageNumber(1); - setHasMore(true); + resetGallery(); }, []); const scrollLeft = useCallback((key) => { @@ -382,22 +300,18 @@ const ImageGallery = () => { Clear )} - {type !== "dateRange" && - selectedFilters[type] && - selectedFilters[type].length > 0 && ( - - )} - - {collapsedFilters[type] ? '+' : '-'} - + {type !== "dateRange" && selectedFilters[type]?.length > 0 && ( + + )} + {collapsedFilters[type] ? '+' : '-'} {!collapsedFilters[type] && ( @@ -412,24 +326,16 @@ const ImageGallery = () => { /> ) : ( - items.map((item) => { - const itemId = item[0]; - const itemName = item[1]; - const isChecked = selectedFilters[type].some( - (selectedItem) => selectedItem[0] === itemId - ); - - return ( - - ); - }) + items.map(([itemId, itemName]) => ( + + )) )} )} @@ -438,39 +344,24 @@ const ImageGallery = () => { 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 userName = `${firstDoc?.uploadedBy?.firstName || ""} ${firstDoc?.uploadedBy?.lastName || ""}`.trim(); + const date = formatUTCToLocalTime(firstDoc?.uploadedAt); const showScrollButtons = batch.documents.length > SCROLL_THRESHOLD; return ( @@ -478,29 +369,15 @@ const ImageGallery = () => {
- +
- - {userName} - - - {date} - + {userName} + {date}
-
-
- {batch.buildingName} > {batch.floorName} >{" "} - {batch.workAreaName || "Unknown"} >{" "} - {batch.activityName} -
+
{batch.buildingName} > {batch.floorName} > {batch.workAreaName || "Unknown"} > {batch.activityName}
{batch.workCategoryName && (
@@ -510,106 +387,52 @@ const ImageGallery = () => { )}
-
- {showScrollButtons && ( - - )} -
(imageGroupRefs.current[batch.batchId] = el)} - > + {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)} - > +
openModal()} onMouseEnter={() => setHoveredImage(doc)} onMouseLeave={() => setHoveredImage(null)}>
{`Image
{hoveredImage === doc && (
-

- Date: {hoverDate} -

-

- Time: {hoverTime} -

-

- Activity: {batch.activityName} -

+

Date: {hoverDate}

+

Time: {hoverTime}

+

Activity: {batch.activityName}

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

- No images match the selected filters. -

+ !loading &&

No images match the selected filters.

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

You've reached the end of the images.

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

You've reached the end of the images.

}
-
+
-
- Filters -
- +
Filters
+
- - + +
{renderFilterCategory("Date Range", [], "dateRange")} @@ -619,12 +442,10 @@ const ImageGallery = () => { {renderFilterCategory("Activity", activities, "activity")} {renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")} {renderFilterCategory("Work Category", workCategories, "workCategory")} - -
); }; -export default ImageGallery; \ No newline at end of file +export default ImageGallery; diff --git a/src/pages/Gallary/ImageGalleryAPI.jsx b/src/repositories/ImageGalleryAPI.jsx similarity index 72% rename from src/pages/Gallary/ImageGalleryAPI.jsx rename to src/repositories/ImageGalleryAPI.jsx index 9ca09bfe..5f75dbed 100644 --- a/src/pages/Gallary/ImageGalleryAPI.jsx +++ b/src/repositories/ImageGalleryAPI.jsx @@ -1,9 +1,8 @@ -import { api } from "../../utils/axiosClient"; +import { api } from "../utils/axiosClient"; export const ImageGalleryAPI = { ImagesGet: (projectId, filter, pageNumber, pageSize) => { const payloadJsonString = JSON.stringify(filter); - // 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 +};