From a94facb062f6111409c041855015262461fbcd4f Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 3 Jul 2025 14:49:18 +0530 Subject: [PATCH] Changes in Directory filter adding clear and apply button and calling api at the time apply filter. --- src/pages/Gallary/ImageGallary.jsx | 380 +++++++++++------ src/pages/Gallary/ImageGallery.css | 590 +++++++++++++++----------- src/pages/Gallary/ImageGalleryAPI.jsx | 7 +- 3 files changed, 615 insertions(+), 362 deletions(-) diff --git a/src/pages/Gallary/ImageGallary.jsx b/src/pages/Gallary/ImageGallary.jsx index 5b3fca0d..ba023adb 100644 --- a/src/pages/Gallary/ImageGallary.jsx +++ b/src/pages/Gallary/ImageGallary.jsx @@ -1,11 +1,11 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useCallback } from "react"; import "./ImageGallery.css"; -import { ImageGalleryAPI } from "./ImageGalleryAPI"; // Assuming this exists +import { ImageGalleryAPI } from "./ImageGalleryAPI"; 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 +import { useSelector } from "react-redux"; +import { useModal } from "./ModalContext"; +import ImagePop from "./ImagePop"; +import Avatar from "../../components/common/Avatar"; const ImageGallery = () => { const [images, setImages] = useState([]); @@ -19,20 +19,41 @@ const ImageGallery = () => { uploadedBy: [], workCategory: [], workArea: [], + startDate: "", + endDate: "", }); - const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false); // Controls filter drawer - const [hoveredImage, setHoveredImage] = useState(null); // For image hover description + const [appliedFilters, setAppliedFilters] = useState({ + buildingIds: null, + floorIds: null, + activityIds: null, + uploadedByIds: null, + workCategoryIds: null, + workAreaIds: null, + startDate: null, + endDate: null, + }); + + const [collapsedFilters, setCollapsedFilters] = useState({ + dateRange: false, + building: false, + floor: false, + activity: false, + uploadedBy: false, + workCategory: false, + workArea: false, + }); + + const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false); + const [hoveredImage, setHoveredImage] = useState(null); 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 + const imageGroupRefs = useRef({}); + const filterPanelRef = useRef(null); + const filterButtonRef = useRef(null); - // 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) && @@ -49,136 +70,250 @@ const ImageGallery = () => { }; }, []); - // Fetch images when the selectedProjectId changes useEffect(() => { - if (!selectedProjectId) return; + if (!selectedProjectId) { + setImages([]); + setLoading(false); + return; + } setLoading(true); - ImageGalleryAPI.ImagesGet(selectedProjectId) + + ImageGalleryAPI.ImagesGet(selectedProjectId, appliedFilters) .then((res) => { setImages(res.data); }) .catch((err) => { console.error("Error fetching images:", err); + setImages([]); }) .finally(() => { setLoading(false); }); - }, [selectedProjectId]); + }, [selectedProjectId, appliedFilters]); - // 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)) + const getUniqueValuesWithIds = useCallback( + (idKey, nameKey) => { + const uniqueMap = new Map(); + images.forEach(img => { + if (img[idKey] && img[nameKey]) { + uniqueMap.set(img[idKey], img[nameKey]); + } + }); + return Array.from(uniqueMap.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); + } + } + }); + 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 uploadedByUsers = getUniqueUploadedByUsers(); + const workCategories = getUniqueValuesWithIds("workCategoryId", "workCategoryName"); + + const toggleFilter = useCallback((type, itemId, itemName) => { + setSelectedFilters((prev) => { + const current = prev[type]; + const isSelected = current.some(item => item[0] === itemId); + + const newArray = isSelected + ? current.filter((item) => item[0] !== itemId) + : [...current, [itemId, itemName]]; + + return { + ...prev, + [type]: newArray, + }; + }); + }, []); + + const handleDateChange = useCallback((type, date) => { + setSelectedFilters((prev) => ({ + ...prev, + [type]: date, + })); + }, []); + + const toggleCollapse = useCallback((type) => { + setCollapsedFilters((prev) => ({ + ...prev, + [type]: !prev[type], + })); + }, []); + + 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, + startDate: selectedFilters.startDate || null, + endDate: selectedFilters.endDate || null, + }; + setAppliedFilters(payload); + setIsFilterPanelOpen(false); + }, [selectedFilters]); + + + const handleClearAllFilters = useCallback(() => { + const initialStateSelected = { + building: [], + floor: [], + activity: [], + uploadedBy: [], + workCategory: [], + workArea: [], + startDate: "", + endDate: "", + }; + setSelectedFilters(initialStateSelected); + + const initialStateApplied = { + buildingIds: null, + floorIds: null, + activityIds: null, + uploadedByIds: null, + workCategoryIds: null, + workAreaIds: null, + startDate: null, + endDate: null, + }; + setAppliedFilters(initialStateApplied); + + setIsFilterPanelOpen(false); + }, []); + + const filteredImages = images.filter( + (img) => { + const uploadedAtMoment = moment(img.uploadedAt); + const startDateMoment = appliedFilters.startDate ? moment(appliedFilters.startDate) : null; + const endDateMoment = appliedFilters.endDate ? moment(appliedFilters.endDate) : null; + + const isWithinDateRange = + (!startDateMoment || uploadedAtMoment.isSameOrAfter(startDateMoment, 'day')) && + (!endDateMoment || uploadedAtMoment.isSameOrBefore(endDateMoment, 'day')); + + const passesCategoryFilters = + (appliedFilters.buildingIds === null || appliedFilters.buildingIds.includes(img.buildingId)) && + (appliedFilters.floorIds === null || appliedFilters.floorIds.includes(img.floorIds)) && + (appliedFilters.activityIds === null || appliedFilters.activityIds.includes(img.activityId)) && + (appliedFilters.workAreaIds === null || appliedFilters.workAreaIds.includes(img.workAreaId)) && + (appliedFilters.uploadedByIds === null || appliedFilters.uploadedByIds.includes(img.uploadedBy?.id)) && + (appliedFilters.workCategoryIds === null || appliedFilters.workCategoryIds.includes(img.workCategoryId)); + + return isWithinDateRange && passesCategoryFilters; + } ); - // 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 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); }); - // Scroll functionality for horizontal image groups - const scrollLeft = (key) => { + const scrollLeft = useCallback((key) => { imageGroupRefs.current[key]?.scrollBy({ left: -200, behavior: "smooth" }); - }; + }, []); - const scrollRight = (key) => { + const scrollRight = useCallback((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 && ( +
+
toggleCollapse(type)}> + {label} +
+ {type === 'dateRange' && (selectedFilters.startDate || selectedFilters.endDate) && ( + )} + {type !== 'dateRange' && selectedFilters[type] && selectedFilters[type].length > 0 && ( + )}
-
- {items.map((item) => ( - - ))}
+ {!collapsedFilters[type] && ( +
+ {type === 'dateRange' ? ( +
+ + +
+ ) : ( + items.map((item) => { + const itemId = item[0]; + const itemName = item[1]; + const isChecked = selectedFilters[type].some(selectedItem => selectedItem[0] === itemId); + + return ( + + ); + }) + )} +
+ )}
); @@ -191,11 +326,8 @@ const ImageGallery = () => {
) : 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"); @@ -224,7 +356,6 @@ const ImageGallery = () => {
- {/* Location and Work Category display */}
{buildingName} > {floorName} > {workArea} >{" "} @@ -239,7 +370,6 @@ const ImageGallery = () => {
- {/* Left scroll arrow */}
- {/* Filter drawer section */}
+ {renderFilterCategory("Date Range", [], "dateRange")} {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 ImageGallery; - +export default ImageGallery; \ No newline at end of file diff --git a/src/pages/Gallary/ImageGallery.css b/src/pages/Gallary/ImageGallery.css index 1795d188..5fc79da0 100644 --- a/src/pages/Gallary/ImageGallery.css +++ b/src/pages/Gallary/ImageGallery.css @@ -1,25 +1,19 @@ .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). - */ + display: grid; grid-template-columns: 1fr 50px; - gap: 0px; /* Gap between main content and filter */ + 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; /* Smooth transition for column resizing */ + transition: grid-template-columns 0.3s ease-in-out; } .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%; @@ -46,19 +40,17 @@ 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 */ + position: relative; + scrollbar-width: none; + -ms-overflow-style: none; } .filter-drawer-wrapper::-webkit-scrollbar { - display: none; /* For Chrome, Safari, and Opera */ + display: none; } .filter-button { @@ -74,31 +66,26 @@ 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 */ + height: 40px; + width: 40px; + z-index: 100; } -/* 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 */ + width: calc(100% - 16px); + height: auto; + padding: 8px 12px; + border-radius: 6px 6px 0 0; + justify-content: space-between; } -/* 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 */ + padding: 0; + font-size: 20px; } .filter-button:hover { @@ -106,9 +93,10 @@ 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 */ + display: flex; + flex-direction: column; + gap: 8px; background-color: #fff; border: 1px solid #e5e7eb; border-radius: 6px; @@ -116,314 +104,442 @@ 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 */ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + max-height: calc(100% - 40px); + padding-top: 37px; 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 */ + max-height: calc(100% - 40px); opacity: 1; transform: translateY(0); - /* Remove top radius to blend with button when open */ - border-top-left-radius: 0; - border-top-right-radius: 0; + overflow-y: auto; + /* padding-bottom: 8px; */ + /* Adjust padding to accommodate the new buttons */ + padding-bottom: -1px; /* Enough space for buttons + some padding */ } -/* ... (rest of your CSS remains the same) ... */ -/* Individual dropdown sections within the filter panel */ +.dropdown { + transition: all 0.3s ease-in-out; +} + .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; + display: block; + position: static; + box-shadow: none; + padding: 4px 10px; + border-radius: 0 0 4px 4px; + max-height: 150px; + overflow-y: auto; + transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; +} + +.dropdown.collapsed .dropdown-content { + max-height: 0; + padding-top: 0; + padding-bottom: 0; + overflow: hidden; +} + +.dropdown-content::-webkit-scrollbar { + width: 6px; +} + +.dropdown-content::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 10px; +} + +.dropdown-content::-webkit-scrollbar-thumb { + background: #c0c0c0; + border-radius: 10px; +} + +.dropdown-content::-webkit-scrollbar-thumb:hover { + background: #a7a7a7; } .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; + display: flex; + align-items: center; + gap: 6px; + padding: 5px 0px; + font-size: 12px; + font-weight: 500; + color: #333; + cursor: pointer; + transition: background 0.2s; } .dropdown-content label:hover { - background-color: #eef2ff; + 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; + -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; + background-color: #6366f1; + border-color: #6366f1; } .dropdown-content input[type="checkbox"]:checked::after { - content: '✔'; - font-size: 10px; - color: white; - position: absolute; + 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; + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 10px; + background-color: #eef2ff; + border-bottom: 1px solid #c7d2fe; + font-weight: bold; + font-size: 13px; + color: #333; + cursor: pointer; + position: sticky; + top: 0; + z-index: 1; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + transition: background-color 0.3s ease, border-bottom 0.3s ease; +} + +.dropdown.collapsed .dropdown-header { + border-bottom: none; + border-radius: 4px; + box-shadow: none; } .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; + font-size: 10px; + background: none; + border: none; + color: #6366f1; + cursor: pointer; + padding: 3px 6px; + border-radius: 4px; + transition: background-color 0.2s ease; + flex-shrink: 0; } .clear-button:hover { - background-color: #eef2ff; + background-color: #eef2ff; +} + +.header-controls { + display: flex; + align-items: center; + gap: 8px; +} + +.toggle-icon { + font-size: 10px; + color: #6366f1; + transition: transform 0.2s ease; +} + +.dropdown .toggle-icon { + transform: rotate(0deg); +} + +.dropdown.collapsed .toggle-icon { + transform: rotate(-90deg); +} + +.date-range-inputs { + display: flex; + flex-direction: column; + gap: 10px; + padding: 5px 0; +} + +.date-range-inputs label { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + font-weight: 500; + color: #333; +} + +.date-input { + flex-grow: 1; + padding: 6px 8px; + border: 1px solid #c7d2fe; + border-radius: 4px; + font-size: 12px; + color: #333; + background-color: #fff; + outline: none; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.date-input:focus { + border-color: #6366f1; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); } -/* --- 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); + 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; + 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; + margin-right: 15px; } .datetime-line { - font-size: 12px; - color: #777; - margin-top: 0px; - + 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; + font-weight: 600; + font-size: 12px; + color: #555; + text-align: right; + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.work-category-display { + margin-top: 4px; + padding: 2px 6px; + border-radius: 5px; + font-size: 12px; + color: #555; } -/* New: Wrapper for image group and arrows */ .image-group-wrapper { - position: relative; - display: flex; - align-items: center; - padding: 0 0px; + 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%; + 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; + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + border-radius: 50%; + width: 30px; + height: 44px; + 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; + opacity: 1; + pointer-events: auto; } .scroll-arrow:hover { - background-color: rgba(0, 0, 0, 0.7); + background-color: rgba(0, 0, 0, 0.7); } .left-arrow { - left: 5px; + left: 5px; } .right-arrow { - right: 5px; + 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; + 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; + display: none; } .image-card:hover { - transform: translateY(-2px) scale(1.03); - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); + 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; + 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; + 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); + opacity: 1; + transform: translateY(0); } .image-hover-description p { - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .spinner-container { - display: flex; - justify-content: center; - align-items: center; - min-height: 200px; + 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; + 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); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } +/* New styles for filter action buttons */ +.filter-actions { + display: flex; + justify-content: space-between; + margin-top: auto; /* Pushes buttons to the bottom */ + padding-top: 10px; + border-top: 1px solid #e5e7eb; + background-color: #fff; + position: sticky; + bottom: 0; + z-index: 10; + padding-bottom: 0px; +} +.apply-filters-button, +.clear-all-button { + padding: 8px 15px; + border-radius: 5px; + font-size: 13px; + cursor: pointer; + transition: background-color 0.2s ease, box-shadow 0.2s ease; + flex: 1; /* Make buttons take equal width */ + margin: 0 4px; /* Add some spacing between them */ +} + +.apply-filters-button { + /* background-color: #6366f1; + color: white; */ + border: none; +} + +.apply-filters-button:hover { + background-color: #4f46e5; + /* box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); */ +} + +.clear-all-button { + border: 1px solid #cbd5e1; +} + +.clear-all-button:hover { + background-color: #e2e8f0; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); +} diff --git a/src/pages/Gallary/ImageGalleryAPI.jsx b/src/pages/Gallary/ImageGalleryAPI.jsx index 0c844b35..df18611d 100644 --- a/src/pages/Gallary/ImageGalleryAPI.jsx +++ b/src/pages/Gallary/ImageGalleryAPI.jsx @@ -2,6 +2,9 @@ import { api } from "../../utils/axiosClient"; export const ImageGalleryAPI = { - ImagesGet: (projectId) => - api.get(`/api/image/images/${projectId}`), + ImagesGet: (projectId, filter) => { + const payloadJsonString = JSON.stringify(filter); + console.log("Applying filters with payload JSON string:", payloadJsonString); + return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}`) + }, } \ No newline at end of file