diff --git a/src/main.tsx b/src/main.tsx
index 78f351eb..ac7175fe 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -8,6 +8,7 @@ import { Provider } from 'react-redux';
import { store } from './store/store';
import { ModalProvider } from './ModalContext.jsx';
import { ChangePasswordProvider } from './components/Context/ChangePasswordContext.jsx';
+import { ModalProvider1 } from './pages/Gallary/ModalContext.jsx';
createRoot(document.getElementById('root')!).render(
@@ -16,7 +17,9 @@ createRoot(document.getElementById('root')!).render(
+
+
diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx
index c0939fcd..5b3fca0d 100644
--- a/src/pages/Gallary/ImageGallary.jsx
+++ b/src/pages/Gallary/ImageGallary.jsx
@@ -1,8 +1,341 @@
-import React from "react";
-import { ComingSoonPage } from "../Misc/ComingSoonPage";
+import React, { useState, useEffect, useRef } from "react";
+import "./ImageGallery.css";
+import { ImageGalleryAPI } from "./ImageGalleryAPI"; // Assuming this exists
+import moment from "moment";
+import { useSelector } from "react-redux"; // Assuming Redux is set up
+import { useModal } from "./ModalContext"; // Assuming ModalContext exists
+import ImagePop from "./ImagePop"; // Assuming ImagePop component exists
+import Avatar from "../../components/common/Avatar"; // Assuming Avatar component exists
-const ImageGallary = () => {
- return ;
+const ImageGallery = () => {
+ const [images, setImages] = useState([]);
+ const selectedProjectId = useSelector((store) => store.localVariables.projectId);
+ const { openModal } = useModal();
+
+ const [selectedFilters, setSelectedFilters] = useState({
+ building: [],
+ floor: [],
+ activity: [],
+ uploadedBy: [],
+ workCategory: [],
+ workArea: [],
+ });
+
+ const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false); // Controls filter drawer
+ const [hoveredImage, setHoveredImage] = useState(null); // For image hover description
+ const [loading, setLoading] = useState(true);
+
+ const imageGroupRefs = useRef({}); // To reference horizontal scroll containers
+ const filterPanelRef = useRef(null); // Ref for the filter panel for click-outside
+ const filterButtonRef = useRef(null); // Ref for the filter button for click-outside
+
+ // Click outside handler for the filter panel
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ // Close filter panel if click is outside the panel and not on the toggle button itself
+ if (
+ filterPanelRef.current &&
+ !filterPanelRef.current.contains(event.target) &&
+ filterButtonRef.current &&
+ !filterButtonRef.current.contains(event.target)
+ ) {
+ setIsFilterPanelOpen(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ // Fetch images when the selectedProjectId changes
+ useEffect(() => {
+ if (!selectedProjectId) return;
+
+ setLoading(true);
+ ImageGalleryAPI.ImagesGet(selectedProjectId)
+ .then((res) => {
+ setImages(res.data);
+ })
+ .catch((err) => {
+ console.error("Error fetching images:", err);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, [selectedProjectId]);
+
+ // Helper functions to get unique filter values
+ const getUniqueValues = (key) => [
+ ...new Set(images.map((img) => img[key]).filter(Boolean)),
+ ];
+
+ const getUniqueUploadedByUsers = () => [
+ ...new Set(
+ images
+ .map((img) => {
+ const firstName = img.uploadedBy?.firstName || "";
+ const lastName = img.uploadedBy?.lastName || "";
+ return `${firstName} ${lastName}`.trim();
+ })
+ .filter(Boolean)
+ ),
+ ];
+
+ const getUniqueWorkCategories = () => [
+ ...new Set(images.map((img) => img.workCategoryName).filter(Boolean)),
+ ];
+
+ // Derive filter options
+ const buildings = getUniqueValues("buildingName");
+ const floors = getUniqueValues("floorName");
+ const activities = getUniqueValues("activityName");
+ const workAreas = getUniqueValues("workAreaName");
+ const uploadedByUsers = getUniqueUploadedByUsers();
+ const workCategories = getUniqueWorkCategories();
+
+ // Toggle selected filters
+ const toggleFilter = (type, value) => {
+ setSelectedFilters((prev) => {
+ const current = prev[type];
+ return {
+ ...prev,
+ [type]: current.includes(value)
+ ? current.filter((v) => v !== value)
+ : [...current, value],
+ };
+ });
+ };
+
+ // Filter images based on selected filters
+ const filteredImages = images.filter(
+ (img) =>
+ (selectedFilters.building.length === 0 ||
+ selectedFilters.building.includes(img.buildingName)) &&
+ (selectedFilters.floor.length === 0 ||
+ selectedFilters.floor.includes(img.floorName)) &&
+ (selectedFilters.activity.length === 0 ||
+ selectedFilters.activity.includes(img.activityName)) &&
+ (selectedFilters.workArea.length === 0 ||
+ selectedFilters.workArea.includes(img.workAreaName)) &&
+ (selectedFilters.uploadedBy.length === 0 ||
+ selectedFilters.uploadedBy.includes(
+ `${img.uploadedBy?.firstName || ""} ${
+ img.uploadedBy?.lastName || ""
+ }`.trim()
+ )) &&
+ (selectedFilters.workCategory.length === 0 ||
+ selectedFilters.workCategory.includes(img.workCategoryName))
+ );
+
+ // Group images by Activity, Uploader, and Work Area
+ const imagesByActivityUser = {};
+ filteredImages.forEach((img) => {
+ const userName = `${img.uploadedBy?.firstName || ""} ${
+ img.uploadedBy?.lastName || ""
+ }`.trim();
+ const workArea = img.workAreaName || "Unknown"; // Handle cases where workAreaName might be null/undefined
+ const key = `${img.activityName}__${userName}__${workArea}`;
+ if (!imagesByActivityUser[key]) imagesByActivityUser[key] = [];
+ imagesByActivityUser[key].push(img);
+ });
+
+ // Scroll functionality for horizontal image groups
+ const scrollLeft = (key) => {
+ imageGroupRefs.current[key]?.scrollBy({ left: -200, behavior: "smooth" });
+ };
+
+ const scrollRight = (key) => {
+ imageGroupRefs.current[key]?.scrollBy({ left: 200, behavior: "smooth" });
+ };
+
+ // Helper function to render filter categories dropdowns
+ const renderFilterCategory = (label, items, type) => (
+
+
+
+ {label}
+ {selectedFilters[type].length > 0 && (
+
+ setSelectedFilters((prev) => ({ ...prev, [type]: [] }))
+ }
+ >
+ Clear
+
+ )}
+
+
+ {items.map((item) => (
+
+ toggleFilter(type, item)}
+ />
+ {item}
+
+ ))}
+
+
+ );
+
+ return (
+
+
+
+ {loading ? (
+
+ ) : Object.entries(imagesByActivityUser).length > 0 ? (
+ // Render each grouped section of images
+ Object.entries(imagesByActivityUser).map(([key, imgs]) => {
+ // Destructure the key to get activity, user, and work area
+ const [activity, userName, workArea] = key.split("__");
+ // Get details from the first image in the group (assuming common details for the group)
+ const { buildingName, floorName, uploadedAt, workCategoryName } =
+ imgs[0];
+ const date = moment(uploadedAt).format("YYYY-MM-DD");
+ const time = moment(uploadedAt).format("hh:mm A");
+
+ return (
+
+
+
+
+
+
+
+ {imgs[0].uploadedBy?.firstName}{" "}
+ {imgs[0].uploadedBy?.lastName}
+
+
+ {date} {time}
+
+
+
+
+
+ {/* Location and Work Category display */}
+
+
+ {buildingName} > {floorName} > {workArea} >{" "}
+ {activity}
+
+ {workCategoryName && (
+
+ {workCategoryName}
+
+ )}
+
+
+
+
+ {/* Left scroll arrow */}
+
scrollLeft(key)}
+ >
+ ‹
+
+
(imageGroupRefs.current[key] = el)}
+ >
+ {/* Render individual image cards within the group */}
+ {imgs.map((img, idx) => {
+ const hoverDate = moment(img.uploadedAt).format(
+ "YYYY-MM-DD"
+ );
+ const hoverTime = moment(img.uploadedAt).format(
+ "hh:mm A"
+ );
+
+ return (
+
openModal(
)}
+ onMouseEnter={() => setHoveredImage(img)}
+ onMouseLeave={() => setHoveredImage(null)}
+ >
+
+
+
+ {/* Hover description for image details */}
+ {hoveredImage === img && (
+
+
+ Date: {hoverDate}
+
+
+ Time: {hoverTime}
+
+
+ Activity: {" "}
+ {img.activityName}
+
+
+ )}
+
+ );
+ })}
+
+ {/* Right scroll arrow */}
+
scrollRight(key)}
+ >
+ ›
+
+
+
+ );
+ })
+ ) : (
+
+ No images match the selected filters.
+
+ )}
+
+
+
+ {/* Filter drawer section */}
+
+
setIsFilterPanelOpen(!isFilterPanelOpen)}
+ ref={filterButtonRef}
+ >
+ {isFilterPanelOpen ? (
+ <>
+ Filter ✖ {/* X Mark when open */}
+ >
+ ) : (
+ ▼
+ )}
+
+
+ {renderFilterCategory("Building", buildings, "building")}
+ {renderFilterCategory("Floor", floors, "floor")}
+ {renderFilterCategory("Work Area", workAreas, "workArea")}
+ {renderFilterCategory("Activity", activities, "activity")}
+ {renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")}
+ {renderFilterCategory("Work Category", workCategories, "workCategory")}
+
+
+
+ );
};
-export default ImageGallary;
+export default ImageGallery;
+
diff --git a/src/pages/Gallary/ImageGallery.css b/src/pages/Gallary/ImageGallery.css
new file mode 100644
index 00000000..1795d188
--- /dev/null
+++ b/src/pages/Gallary/ImageGallery.css
@@ -0,0 +1,429 @@
+.gallery-container {
+ display: grid; /* Use CSS Grid for layout */
+ /*
+ * MODIFIED: When filter is closed, main content takes 1fr,
+ * and the filter column is just big enough for the small floating button (e.g., 50px).
+ */
+ grid-template-columns: 1fr 50px;
+ gap: 0px; /* Gap between main content and filter */
+ 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; /* Smooth transition for column resizing */
+}
+
+.gallery-container.filter-panel-open {
+ /* When open, main content shrinks, filter expands to 250px */
+ grid-template-columns: 1fr 250px;
+}
+
+/* Main content area (images) - No changes needed here */
+.main-content {
+ overflow-y: auto;
+ max-height: 100%;
+ box-sizing: border-box;
+ scrollbar-width: thin;
+ scrollbar-color: #a7a7a7 #f1f1f1;
+}
+
+.main-content::-webkit-scrollbar {
+ width: 8px;
+}
+
+.main-content::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 10px;
+}
+
+.main-content::-webkit-scrollbar-thumb {
+ background: #a7a7a7;
+ border-radius: 10px;
+}
+
+.main-content::-webkit-scrollbar-thumb:hover {
+ background: #888;
+}
+
+/* New: Wrapper for the filter drawer and its button */
+.filter-drawer-wrapper {
+ flex-shrink: 0;
+ max-height: 100%;
+ box-sizing: border-box;
+ position: relative; /* Essential for positioning the filter panel within */
+ /* Hides the scrollbar from the wrapper itself, as the panel will handle its own scrolling. */
+ scrollbar-width: none; /* For Firefox */
+ -ms-overflow-style: none; /* For IE and Edge */
+}
+
+.filter-drawer-wrapper::-webkit-scrollbar {
+ display: none; /* For Chrome, Safari, and Opera */
+}
+
+.filter-button {
+ background-color: #6366f1;
+ color: white;
+ padding: 8px 12px;
+ font-size: 14px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+ /* Added padding to transition properties for smoothness */
+ 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;
+
+ /* Floating / Positioning */
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 40px; /* Fixed height when closed */
+ width: 40px; /* Fixed width when closed (making it square) */
+ z-index: 100; /* Ensure it stays on top */
+}
+
+/* When the filter panel is open, the button should match its width and blend with the panel */
+.gallery-container.filter-panel-open .filter-button {
+ width: calc(100% - 16px); /* Match filter-panel width minus its padding (8px on each side) */
+ height: auto; /* Allow height to adjust for text content */
+ padding: 8px 12px; /* Restore padding when expanded */
+ border-radius: 6px 6px 0 0; /* Adjust border-radius to blend with panel below */
+ justify-content: space-between; /* Space between "Filter" text and "X" icon */
+}
+
+/* Add a class for the button when the panel is closed to show only icon */
+.filter-button.closed-icon {
+ padding: 0; /* Remove padding to make it compact */
+ font-size: 20px; /* Make icon larger when it's just an icon */
+}
+
+.filter-button:hover {
+ background-color: #4f46e5;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+}
+
+/* The actual panel that contains all filter categories */
+.filter-panel {
+ display: flex; /* Always display as flex to manage children */
+ background-color: #fff;
+ border: 1px solid #e5e7eb;
+ border-radius: 6px;
+ padding: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ width: 100%;
+ box-sizing: border-box;
+ flex-direction: column;
+ gap: 12px;
+ max-height: 0;
+ /* Use overflow-y: hidden for the max-height transition to work smoothly */
+ overflow-y: hidden;
+ opacity: 0;
+ transform: translateY(-10px);
+ /* Added border-radius to transition properties for smoothness */
+ transition: max-height 0.3s ease-out, opacity 0.3s ease-out, transform 0.3s ease-out, border-radius 0.3s ease-in-out;
+
+ /* Position it below the button when open */
+ margin-top: 40px; /* Account for the button's fixed height */
+}
+
+.filter-panel.open {
+ max-height: 1000px; /* A value larger than the expected height of content */
+ opacity: 1;
+ transform: translateY(0);
+ /* Remove top radius to blend with button when open */
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+
+/* ... (rest of your CSS remains the same) ... */
+
+/* Individual dropdown sections within the filter panel */
+.dropdown-content {
+ display: block !important;
+ position: static;
+ background-color: #f9fafb;
+ box-shadow: none;
+ padding: 4px 0;
+ margin-top: 0;
+ border-radius: 0 0 4px 4px;
+ max-height: unset;
+ overflow-y: visible;
+}
+
+.dropdown-content label {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 5px 10px;
+ font-size: 12px;
+ font-weight: 500;
+ color: #333;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.dropdown-content label:hover {
+ background-color: #eef2ff;
+}
+
+.dropdown-content input[type="checkbox"] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 3px;
+ border: 1px solid #c7d2fe;
+ background-color: #fff;
+ cursor: pointer;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease-in-out;
+}
+
+.dropdown-content input[type="checkbox"]:checked {
+ background-color: #6366f1;
+ border-color: #6366f1;
+}
+
+.dropdown-content input[type="checkbox"]:checked::after {
+ content: '✔';
+ font-size: 10px;
+ color: white;
+ position: absolute;
+}
+
+
+.dropdown-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 10px 5px;
+ border-bottom: 1px solid #eee;
+ margin-bottom: 6px;
+}
+
+.clear-button {
+ font-size: 10px;
+ background: none;
+ border: none;
+ color: #6366f1;
+ cursor: pointer;
+ padding: 3px 6px;
+ border-radius: 4px;
+ transition: background-color 0.2s ease;
+}
+
+.clear-button:hover {
+ background-color: #eef2ff;
+}
+
+/* --- Image Card Section --- */
+.grouped-section {
+ margin-bottom: 5px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ padding: 10px;
+ background-color: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.group-heading {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 3px;
+ font-size: 12px;
+ flex-wrap: wrap;
+ padding-bottom: 8px;
+ border-bottom: 1px dashed #eee;
+}
+
+.group-heading > div {
+ margin-right: 15px;
+}
+
+.datetime-line {
+ font-size: 12px;
+ color: #777;
+ margin-top: 0px;
+
+}
+
+.location-line {
+ font-weight: 600;
+ font-size: 12px;
+ color: #555;
+ text-align: right; /* Keep this if you want the whole block aligned right */
+ display: flex; /* Use flexbox to manage children's layout */
+ flex-direction: column; /* Stack children vertically */
+ align-items: flex-end; /* Align items to the end (right) if text-align: right is desired */
+}
+.work-category-display {
+ /* Basic styling for the work category, if needed */
+ margin-top: 4px; /* Add some space above it */
+ padding: 2px 6px;
+ /* A light background for better visibility */
+ border-radius: 5px;
+ font-size: 12px;
+ /* Override the parent's bold if desired */
+ color: #555;
+}
+
+/* New: Wrapper for image group and arrows */
+.image-group-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 0 0px;
+}
+
+.image-group-horizontal {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ overflow-x: hidden;
+ gap: 3px;
+ padding-bottom: 8px;
+ -webkit-overflow-scrolling: touch;
+ scroll-behavior: smooth;
+ width: 100%;
+}
+.scroll-arrow {
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ border: none;
+ border-radius: 50%; /* This makes it a circle */
+ width: 30px; /* Ensure width and height are equal */
+ height: 44px; /* Ensure width and height are equal */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ font-size: 18px;
+ z-index: 10;
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ opacity: 0;
+ transition: opacity 0.3s ease, background-color 0.2s ease;
+ pointer-events: none;
+}
+
+.image-group-wrapper:hover .scroll-arrow {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.scroll-arrow:hover {
+ background-color: rgba(0, 0, 0, 0.7);
+}
+
+.left-arrow {
+ left: 5px;
+}
+
+.right-arrow {
+ right: 5px;
+}
+
+.image-card {
+ width: 150px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ background: #fff;
+ cursor: pointer;
+ overflow: hidden;
+ flex-shrink: 0;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+ position: relative;
+}
+
+hr {
+ margin: 0rem 0;
+ color: var(--bs-border-color);
+ border: 0;
+ border-top: var(--bs-border-width) solid;
+ opacity: 1;
+}
+
+.image-card:hover {
+ transform: translateY(-2px) scale(1.03);
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
+}
+
+.image-wrapper img {
+ width: 100%;
+ height: 100px;
+ object-fit: cover;
+ display: block;
+ border-radius: 8px 8px 0 0;
+}
+
+/* NEW: Styles for the hover description */
+.image-hover-description {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ background-color: rgba(0, 0, 0, 0.75);
+ color: white;
+ padding: 5px 8px;
+ box-sizing: border-box;
+ font-size: 11px;
+ line-height: 1.4;
+ text-align: left;
+ opacity: 0;
+ transform: translateY(100%);
+ transition: opacity 0.2s ease-out, transform 0.2s ease-out;
+ border-bottom-left-radius: 8px;
+ border-bottom-right-radius: 8px;
+ pointer-events: none;
+}
+
+.image-card:hover .image-hover-description {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.image-hover-description p {
+ margin: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.spinner-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 200px;
+}
+
+.spinner {
+ border: 6px solid #f3f3f3;
+ border-top: 6px solid #6658f6;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+
diff --git a/src/pages/Gallary/ImageGalleryAPI.jsx b/src/pages/Gallary/ImageGalleryAPI.jsx
new file mode 100644
index 00000000..0c844b35
--- /dev/null
+++ b/src/pages/Gallary/ImageGalleryAPI.jsx
@@ -0,0 +1,7 @@
+import { api } from "../../utils/axiosClient";
+
+export const ImageGalleryAPI = {
+
+ ImagesGet: (projectId) =>
+ api.get(`/api/image/images/${projectId}`),
+}
\ No newline at end of file
diff --git a/src/pages/Gallary/ImagePop.css b/src/pages/Gallary/ImagePop.css
new file mode 100644
index 00000000..dfdb549c
--- /dev/null
+++ b/src/pages/Gallary/ImagePop.css
@@ -0,0 +1,103 @@
+.image-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 9999; /* High z-index to ensure it's on top */
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.85); /* Dark semi-transparent background */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.image-modal-content {
+ background: #fff;
+ padding: 24px;
+ max-width: 90%; /* Responsive max-width */
+ max-height: 100%; /* Responsive max-height */
+ border-radius: 12px;
+ position: relative;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
+ text-align: center;
+ display: flex; /* Use flexbox for internal layout */
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-image {
+ max-width: 100%;
+ max-height: 70vh; /* Limits image height to 70% of viewport height */
+ border-radius: 10px;
+ object-fit: contain; /* Ensures the whole image is visible without cropping */
+ margin-bottom: 20px;
+ flex-shrink: 0; /* Prevent image from shrinking if content is too large */
+}
+
+.image-details {
+ text-align: left;
+ color: #444;
+ font-size: 14px;
+ line-height: 1.4;
+ margin: 0;
+ padding: 0;
+ width: 100%; /* Ensure details section takes full width */
+}
+
+.image-details p {
+ margin: 4px 0; /* Reduce vertical space between lines in details */
+}
+
+.close-button {
+ position: absolute;
+ top: 1px; /* Position relative to the modal content */
+ right: 8px;
+ font-size: 30px;
+ background: none;
+ border: none;
+ color: black; /* White color for visibility on dark overlay */
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+ z-index: 10000; /* Ensure it's above everything else */
+}
+
+/* Styles for the navigation buttons */
+.nav-button {
+ position: absolute;
+ top: 50%; /* Vertically center them */
+ transform: translateY(-50%); /* Adjust for perfect vertical centering */
+ background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
+ color: white;
+ border: none;
+ padding: 10px 15px;
+ font-size: 30px;
+ cursor: pointer;
+ z-index: 1000; /* Ensure buttons are above the image */
+ border-radius: 50%; /* Make them circular */
+ width: 50px;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.3s ease; /* Smooth hover effect */
+}
+
+.nav-button:hover {
+ background-color: rgba(0, 0, 0, 0.8); /* Darker on hover */
+}
+
+.nav-button.prev-button {
+ left: 0px; /* Position left arrow */
+}
+
+.nav-button.next-button {
+ right: 0px; /* Position right arrow */
+}
+
+/* Style for disabled buttons (optional) */
+.nav-button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
diff --git a/src/pages/Gallary/ImagePop.jsx b/src/pages/Gallary/ImagePop.jsx
new file mode 100644
index 00000000..62f4b8da
--- /dev/null
+++ b/src/pages/Gallary/ImagePop.jsx
@@ -0,0 +1,94 @@
+import React, { useState, useEffect } from "react";
+import "./ImagePop.css";
+import { useModal } from "./ModalContext";
+import moment from "moment";
+
+const ImagePop = ({ images, 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)
+ useEffect(() => {
+ setCurrentIndex(initialIndex);
+ }, [initialIndex, images]);
+
+ // If no images are provided or the array is empty, don't render
+ if (!images || images.length === 0) return null;
+
+ // Get the current image based on currentIndex
+ const image = images[currentIndex];
+
+ // Fallback if for some reason the image at the current index doesn't exist
+ if (!image) return null;
+
+ // Format details for display
+ 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");
+
+ // Handler for navigating to the previous image
+ const handlePrev = () => {
+ setCurrentIndex((prevIndex) => Math.max(0, prevIndex - 1));
+ };
+
+ // Handler for navigating to the next image
+ const handleNext = () => {
+ setCurrentIndex((prevIndex) =>
+ Math.min(images.length - 1, prevIndex + 1)
+ );
+ };
+
+ // Determine if previous/next buttons should be enabled/visible
+ const hasPrev = currentIndex > 0;
+ const hasNext = currentIndex < images.length - 1;
+
+ return (
+
+
+ {/* Close button */}
+
+ ×
+
+
+ {/* Previous button, only shown if there's a previous image */}
+ {hasPrev && (
+
+ ‹ {/* Unicode for single left angle quotation mark */}
+
+ )}
+
+ {/* The main image display */}
+
+
+ {/* Next button, only shown if there's a next image */}
+ {hasNext && (
+
+ › {/* Unicode for single right angle quotation mark */}
+
+ )}
+
+ {/* Image details */}
+
+
+ 👤 Uploaded By: {fullName}
+
+
+ 📅 Date: {date} {time}
+
+
+ 🏢 Location: {image.buildingName} >{" "}
+ {image.floorName} > {image.activityName}
+
+
+ 📝 Comments: {image.comment}
+
+
+
+
+ );
+};
+
+export default ImagePop;
\ No newline at end of file
diff --git a/src/pages/Gallary/ModalContext.jsx b/src/pages/Gallary/ModalContext.jsx
new file mode 100644
index 00000000..a84450f5
--- /dev/null
+++ b/src/pages/Gallary/ModalContext.jsx
@@ -0,0 +1,23 @@
+import React, { createContext, useContext, useState } from "react";
+
+const ModalContext = createContext();
+
+export const ModalProvider1 = ({ children }) => {
+ const [modalContent, setModalContent] = useState(null);
+
+ const openModal = (content) => setModalContent(content);
+ const closeModal = () => setModalContent(null);
+
+ return (
+
+ {children}
+ {modalContent && (
+
+ {modalContent}
+
+ )}
+
+ );
+};
+
+export const useModal = () => useContext(ModalContext);