Adding Date Range picker in filter dropdown.

This commit is contained in:
Kartik sharma 2025-07-04 10:18:18 +05:30
parent a94facb062
commit 09906420a6
3 changed files with 179 additions and 131 deletions

View File

@ -1,3 +1,4 @@
// ImageGallery.js
import React, { useState, useEffect, useRef, useCallback } from "react"; import React, { useState, useEffect, useRef, useCallback } from "react";
import "./ImageGallery.css"; import "./ImageGallery.css";
import { ImageGalleryAPI } from "./ImageGalleryAPI"; import { ImageGalleryAPI } from "./ImageGalleryAPI";
@ -6,12 +7,15 @@ import { useSelector } from "react-redux";
import { useModal } from "./ModalContext"; import { useModal } from "./ModalContext";
import ImagePop from "./ImagePop"; import ImagePop from "./ImagePop";
import Avatar from "../../components/common/Avatar"; import Avatar from "../../components/common/Avatar";
import DateRangePicker from "../../components/common/DateRangePicker"; // Assuming this is the path to your DateRangePicker
const ImageGallery = () => { const ImageGallery = () => {
const [images, setImages] = useState([]); const [images, setImages] = useState([]);
const selectedProjectId = useSelector((store) => store.localVariables.projectId); const selectedProjectId = useSelector((store) => store.localVariables.projectId);
const { openModal } = useModal(); const { openModal } = useModal();
const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD');
const [selectedFilters, setSelectedFilters] = useState({ const [selectedFilters, setSelectedFilters] = useState({
building: [], building: [],
floor: [], floor: [],
@ -95,7 +99,7 @@ const ImageGallery = () => {
const getUniqueValuesWithIds = useCallback( const getUniqueValuesWithIds = useCallback(
(idKey, nameKey) => { (idKey, nameKey) => {
const uniqueMap = new Map(); const uniqueMap = new Map();
images.forEach(img => { images.forEach((img) => {
if (img[idKey] && img[nameKey]) { if (img[idKey] && img[nameKey]) {
uniqueMap.set(img[idKey], img[nameKey]); uniqueMap.set(img[idKey], img[nameKey]);
} }
@ -105,21 +109,20 @@ const ImageGallery = () => {
[images] [images]
); );
const getUniqueUploadedByUsers = useCallback( const getUniqueUploadedByUsers = useCallback(() => {
() => { const uniqueUsersMap = new Map();
const uniqueUsersMap = new Map(); images.forEach((img) => {
images.forEach(img => { if (img.uploadedBy && img.uploadedBy.id) {
if (img.uploadedBy && img.uploadedBy.id) { const fullName = `${img.uploadedBy.firstName || ""} ${
const fullName = `${img.uploadedBy.firstName || ""} ${img.uploadedBy.lastName || ""}`.trim(); img.uploadedBy.lastName || ""
if (fullName) { }`.trim();
uniqueUsersMap.set(img.uploadedBy.id, fullName); if (fullName) {
} uniqueUsersMap.set(img.uploadedBy.id, fullName);
} }
}); }
return Array.from(uniqueUsersMap.entries()); });
}, return Array.from(uniqueUsersMap.entries());
[images] }, [images]);
);
const buildings = getUniqueValuesWithIds("buildingId", "buildingName"); const buildings = getUniqueValuesWithIds("buildingId", "buildingName");
const floors = getUniqueValuesWithIds("floorIds", "floorName"); const floors = getUniqueValuesWithIds("floorIds", "floorName");
@ -131,7 +134,7 @@ const ImageGallery = () => {
const toggleFilter = useCallback((type, itemId, itemName) => { const toggleFilter = useCallback((type, itemId, itemName) => {
setSelectedFilters((prev) => { setSelectedFilters((prev) => {
const current = prev[type]; const current = prev[type];
const isSelected = current.some(item => item[0] === itemId); const isSelected = current.some((item) => item[0] === itemId);
const newArray = isSelected const newArray = isSelected
? current.filter((item) => item[0] !== itemId) ? current.filter((item) => item[0] !== itemId)
@ -144,10 +147,11 @@ const ImageGallery = () => {
}); });
}, []); }, []);
const handleDateChange = useCallback((type, date) => { const setDateRange = useCallback(({ startDate, endDate }) => {
setSelectedFilters((prev) => ({ setSelectedFilters((prev) => ({
...prev, ...prev,
[type]: date, startDate: startDate || "",
endDate: endDate || "",
})); }));
}, []); }, []);
@ -160,12 +164,30 @@ const ImageGallery = () => {
const handleApplyFilters = useCallback(() => { const handleApplyFilters = useCallback(() => {
const payload = { const payload = {
buildingIds: selectedFilters.building.length > 0 ? selectedFilters.building.map(item => item[0]) : null, buildingIds:
floorIds: selectedFilters.floor.length > 0 ? selectedFilters.floor.map(item => item[0]) : null, selectedFilters.building.length > 0
workAreaIds: selectedFilters.workArea.length > 0 ? selectedFilters.workArea.map(item => item[0]) : null, ? selectedFilters.building.map((item) => item[0])
workCategoryIds: selectedFilters.workCategory.length > 0 ? selectedFilters.workCategory.map(item => item[0]) : null, : null,
activityIds: selectedFilters.activity.length > 0 ? selectedFilters.activity.map(item => item[0]) : null, floorIds:
uploadedByIds: selectedFilters.uploadedBy.length > 0 ? selectedFilters.uploadedBy.map(item => item[0]) : null, 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, startDate: selectedFilters.startDate || null,
endDate: selectedFilters.endDate || null, endDate: selectedFilters.endDate || null,
}; };
@ -173,7 +195,6 @@ const ImageGallery = () => {
setIsFilterPanelOpen(false); setIsFilterPanelOpen(false);
}, [selectedFilters]); }, [selectedFilters]);
const handleClearAllFilters = useCallback(() => { const handleClearAllFilters = useCallback(() => {
const initialStateSelected = { const initialStateSelected = {
building: [], building: [],
@ -202,32 +223,41 @@ const ImageGallery = () => {
setIsFilterPanelOpen(false); setIsFilterPanelOpen(false);
}, []); }, []);
const filteredImages = images.filter( const filteredImages = images.filter((img) => {
(img) => { const uploadedAtMoment = moment(img.uploadedAt);
const uploadedAtMoment = moment(img.uploadedAt); const startDateMoment = appliedFilters.startDate
const startDateMoment = appliedFilters.startDate ? moment(appliedFilters.startDate) : null; ? moment(appliedFilters.startDate)
const endDateMoment = appliedFilters.endDate ? moment(appliedFilters.endDate) : null; : null;
const endDateMoment = appliedFilters.endDate
? moment(appliedFilters.endDate)
: null;
const isWithinDateRange = const isWithinDateRange =
(!startDateMoment || uploadedAtMoment.isSameOrAfter(startDateMoment, 'day')) && (!startDateMoment || uploadedAtMoment.isSameOrAfter(startDateMoment, "day")) &&
(!endDateMoment || uploadedAtMoment.isSameOrBefore(endDateMoment, 'day')); (!endDateMoment || uploadedAtMoment.isSameOrBefore(endDateMoment, "day"));
const passesCategoryFilters = const passesCategoryFilters =
(appliedFilters.buildingIds === null || appliedFilters.buildingIds.includes(img.buildingId)) && (appliedFilters.buildingIds === null ||
(appliedFilters.floorIds === null || appliedFilters.floorIds.includes(img.floorIds)) && appliedFilters.buildingIds.includes(img.buildingId)) &&
(appliedFilters.activityIds === null || appliedFilters.activityIds.includes(img.activityId)) && (appliedFilters.floorIds === null ||
(appliedFilters.workAreaIds === null || appliedFilters.workAreaIds.includes(img.workAreaId)) && appliedFilters.floorIds.includes(img.floorIds)) &&
(appliedFilters.uploadedByIds === null || appliedFilters.uploadedByIds.includes(img.uploadedBy?.id)) && (appliedFilters.activityIds === null ||
(appliedFilters.workCategoryIds === null || appliedFilters.workCategoryIds.includes(img.workCategoryId)); 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; return isWithinDateRange && passesCategoryFilters;
} });
);
const imagesByActivityUser = {}; const imagesByActivityUser = {};
filteredImages.forEach((img) => { filteredImages.forEach((img) => {
const userName = `${img.uploadedBy?.firstName || ""} ${img.uploadedBy?.lastName || "" const userName = `${img.uploadedBy?.firstName || ""} ${
}`.trim(); img.uploadedBy?.lastName || ""
}`.trim();
const workArea = img.workAreaName || "Unknown"; const workArea = img.workAreaName || "Unknown";
const key = `${img.activityName}__${userName}__${workArea}`; const key = `${img.activityName}__${userName}__${workArea}`;
if (!imagesByActivityUser[key]) imagesByActivityUser[key] = []; if (!imagesByActivityUser[key]) imagesByActivityUser[key] = [];
@ -243,62 +273,48 @@ const ImageGallery = () => {
}, []); }, []);
const renderFilterCategory = (label, items, type) => ( const renderFilterCategory = (label, items, type) => (
<div className={`dropdown ${collapsedFilters[type] ? 'collapsed' : ''}`}> <div className={`dropdown ${collapsedFilters[type] ? "collapsed" : ""}`}>
<div className="dropdown-header" onClick={() => toggleCollapse(type)}> <div className="dropdown-header" onClick={() => toggleCollapse(type)}>
<strong>{label}</strong> <strong>{label}</strong>
<div className="header-controls"> <div className="header-controls">
{type === 'dateRange' && (selectedFilters.startDate || selectedFilters.endDate) && ( {type === "dateRange" && (selectedFilters.startDate || selectedFilters.endDate) && (
<button <button
className="clear-button" className="clear-button"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setSelectedFilters(prev => ({ ...prev, startDate: "", endDate: "" })); setSelectedFilters((prev) => ({ ...prev, startDate: "", endDate: "" }));
}} }}
> >
Clear Clear
</button> </button>
)} )}
{type !== 'dateRange' && selectedFilters[type] && selectedFilters[type].length > 0 && ( {type !== "dateRange" &&
<button selectedFilters[type] &&
className="clear-button" selectedFilters[type].length > 0 && (
onClick={(e) => { <button
e.stopPropagation(); className="clear-button"
setSelectedFilters((prev) => ({ ...prev, [type]: [] })); onClick={(e) => {
}} e.stopPropagation();
> setSelectedFilters((prev) => ({ ...prev, [type]: [] }));
Clear }}
</button> >
)} Clear
</button>
)}
</div> </div>
</div> </div>
{!collapsedFilters[type] && ( {!collapsedFilters[type] && (
<div className="dropdown-content"> <div className="dropdown-content">
{type === 'dateRange' ? ( {type === "dateRange" ? (
<div className="date-range-inputs"> // The DateRangePicker will be rendered outside this function, at the end of the filter panel
<label> null
From:
<input
type="date"
value={selectedFilters.startDate}
onChange={(e) => handleDateChange("startDate", e.target.value)}
className="date-input"
/>
</label>
<label>
To:
<input
type="date"
value={selectedFilters.endDate}
onChange={(e) => handleDateChange("endDate", e.target.value)}
className="date-input"
/>
</label>
</div>
) : ( ) : (
items.map((item) => { items.map((item) => {
const itemId = item[0]; const itemId = item[0];
const itemName = item[1]; const itemName = item[1];
const isChecked = selectedFilters[type].some(selectedItem => selectedItem[0] === itemId); const isChecked = selectedFilters[type].some(
(selectedItem) => selectedItem[0] === itemId
);
return ( return (
<label key={itemId}> <label key={itemId}>
@ -328,8 +344,7 @@ const ImageGallery = () => {
) : Object.entries(imagesByActivityUser).length > 0 ? ( ) : Object.entries(imagesByActivityUser).length > 0 ? (
Object.entries(imagesByActivityUser).map(([key, imgs]) => { Object.entries(imagesByActivityUser).map(([key, imgs]) => {
const [activity, userName, workArea] = key.split("__"); const [activity, userName, workArea] = key.split("__");
const { buildingName, floorName, uploadedAt, workCategoryName } = const { buildingName, floorName, uploadedAt, workCategoryName } = imgs[0];
imgs[0];
const date = moment(uploadedAt).format("YYYY-MM-DD"); const date = moment(uploadedAt).format("YYYY-MM-DD");
const time = moment(uploadedAt).format("hh:mm A"); const time = moment(uploadedAt).format("hh:mm A");
@ -381,18 +396,16 @@ const ImageGallery = () => {
ref={(el) => (imageGroupRefs.current[key] = el)} ref={(el) => (imageGroupRefs.current[key] = el)}
> >
{imgs.map((img, idx) => { {imgs.map((img, idx) => {
const hoverDate = moment(img.uploadedAt).format( const hoverDate = moment(img.uploadedAt).format("YYYY-MM-DD");
"YYYY-MM-DD" const hoverTime = moment(img.uploadedAt).format("hh:mm A");
);
const hoverTime = moment(img.uploadedAt).format(
"hh:mm A"
);
return ( return (
<div <div
key={img.imageUrl} key={img.imageUrl}
className="image-card" className="image-card"
onClick={() => openModal(<ImagePop images={imgs} initialIndex={idx} />)} onClick={() =>
openModal(<ImagePop images={imgs} initialIndex={idx} />)
}
onMouseEnter={() => setHoveredImage(img)} onMouseEnter={() => setHoveredImage(img)}
onMouseLeave={() => setHoveredImage(null)} onMouseLeave={() => setHoveredImage(null)}
> >
@ -408,8 +421,7 @@ const ImageGallery = () => {
<strong>Time:</strong> {hoverTime} <strong>Time:</strong> {hoverTime}
</p> </p>
<p> <p>
<strong>Activity:</strong>{" "} <strong>Activity:</strong> {img.activityName}
{img.activityName}
</p> </p>
</div> </div>
)} )}
@ -450,7 +462,33 @@ const ImageGallery = () => {
)} )}
</button> </button>
<div className={`filter-panel ${isFilterPanelOpen ? "open" : ""}`} ref={filterPanelRef}> <div className={`filter-panel ${isFilterPanelOpen ? "open" : ""}`} ref={filterPanelRef}>
{renderFilterCategory("Date Range", [], "dateRange")} <div className={`dropdown ${collapsedFilters.dateRange ? 'collapsed' : ''}`}>
<div className="dropdown-header" onClick={() => toggleCollapse('dateRange')}>
<strong>Date Range</strong>
{(selectedFilters.startDate || selectedFilters.endDate) && (
<button
className="clear-button"
onClick={(e) => {
e.stopPropagation();
setSelectedFilters(prev => ({ ...prev, startDate: "", endDate: "" }));
}}
>
Clear
</button>
)}
</div>
{!collapsedFilters.dateRange && (
<div >
<DateRangePicker
onRangeChange={setDateRange}
defaultStartDate={selectedFilters.startDate || yesterday} // Use selected date or yesterday as default
defaultEndDate={selectedFilters.endDate || moment().format('YYYY-MM-DD')} // Use selected date or today as default
startDate={selectedFilters.startDate} // Pass current selected start date
endDate={selectedFilters.endDate} // Pass current selected end date
/>
</div>
)}
</div>
{renderFilterCategory("Building", buildings, "building")} {renderFilterCategory("Building", buildings, "building")}
{renderFilterCategory("Floor", floors, "floor")} {renderFilterCategory("Floor", floors, "floor")}
{renderFilterCategory("Work Area", workAreas, "workArea")} {renderFilterCategory("Work Area", workAreas, "workArea")}
@ -458,6 +496,9 @@ const ImageGallery = () => {
{renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")} {renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")}
{renderFilterCategory("Work Category", workCategories, "workCategory")} {renderFilterCategory("Work Category", workCategories, "workCategory")}
{/* DateRangePicker at the end */}
<div className="filter-actions"> <div className="filter-actions">
<button className="btn btn-secondary btn-xs " onClick={handleClearAllFilters}> <button className="btn btn-secondary btn-xs " onClick={handleClearAllFilters}>
Clear All Clear All

View File

@ -1,3 +1,4 @@
/* ImageGallery.css */
.gallery-container { .gallery-container {
display: grid; display: grid;
grid-template-columns: 1fr 50px; grid-template-columns: 1fr 50px;
@ -66,7 +67,8 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
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; 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; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -76,7 +78,7 @@
} }
.gallery-container.filter-panel-open .filter-button { .gallery-container.filter-panel-open .filter-button {
width: calc(100% - 16px); width: calc(100% - 1px);
height: auto; height: auto;
padding: 8px 12px; padding: 8px 12px;
border-radius: 6px 6px 0 0; border-radius: 6px 6px 0 0;
@ -114,21 +116,18 @@
overflow-y: hidden; overflow-y: hidden;
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
transition: max-height 0.3s ease-out, opacity 0.3s ease-out, transform 0.3s ease-out, border-radius 0.3s ease-in-out; transition: max-height 0.3s ease-out, opacity 0.3s ease-out,
transform 0.3s ease-out, border-radius 0.3s ease-in-out;
} }
.filter-panel.open { .filter-panel.open {
max-height: calc(100% - 40px); max-height: calc(100% - 0px);
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
overflow-y: auto; overflow-y: auto;
/* padding-bottom: 8px; */ padding-bottom: 0px; /* Adjusted padding-bottom */
/* Adjust padding to accommodate the new buttons */
padding-bottom: -1px; /* Enough space for buttons + some padding */
} }
.dropdown { .dropdown {
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
@ -139,8 +138,8 @@
box-shadow: none; box-shadow: none;
padding: 4px 10px; padding: 4px 10px;
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
max-height: 150px; max-height: 150px; /* Default max-height for scrollable dropdowns */
overflow-y: auto; overflow-y: auto; /* Default overflow for scrollable dropdowns */
transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out;
} }
@ -169,6 +168,17 @@
background: #a7a7a7; background: #a7a7a7;
} }
/* --- New styles for Date Range Picker dropdown --- */
.dropdown strong:contains("Date Range") + .header-controls + .dropdown-content {
max-height: none; /* Remove max-height */
overflow-y: visible; /* Allow content to dictate height */
}
.dropdown strong:contains("Date Range") + .header-controls + .dropdown-content::-webkit-scrollbar {
display: none; /* Hide scrollbar for Webkit browsers */
}
/* --- End new styles --- */
.dropdown-content label { .dropdown-content label {
display: flex; display: flex;
align-items: center; align-items: center;
@ -208,7 +218,7 @@
} }
.dropdown-content input[type="checkbox"]:checked::after { .dropdown-content input[type="checkbox"]:checked::after {
content: '✔'; content: "✔";
font-size: 10px; font-size: 10px;
color: white; color: white;
position: absolute; position: absolute;
@ -499,47 +509,45 @@ hr {
} }
} }
/* New styles for filter action buttons */
.filter-actions { .filter-actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: auto; /* Pushes buttons to the bottom */ margin-top: auto;
padding-top: 10px; padding-top: 10px;
border-top: 1px solid #e5e7eb; border-top: 1px solid #e5e7eb;
background-color: #fff; background-color: #fff;
position: sticky; position: sticky;
bottom: 0; bottom: 0;
z-index: 10; z-index: 10;
padding-bottom: 0px; padding-bottom: 5px;
} }
.apply-filters-button, .btn {
.clear-all-button {
padding: 8px 15px; padding: 8px 15px;
border-radius: 5px; border-radius: 5px;
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease; transition: background-color 0.2s ease, box-shadow 0.2s ease;
flex: 1; /* Make buttons take equal width */ flex: 1;
margin: 0 4px; /* Add some spacing between them */ margin: 0 4px;
} }
.apply-filters-button { .btn-primary {
/* background-color: #6366f1; background-color: #6366f1;
color: white; */ color: white;
border: none; border: none;
} }
.apply-filters-button:hover { .btn-primary:hover {
background-color: #4f46e5; background-color: #4f46e5;
/* box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); */
} }
.clear-all-button { .btn-secondary {
border: 1px solid #cbd5e1; border: 1px solid #cbd5e1;
background-color: #fff;
color: #333;
} }
.clear-all-button:hover { .btn-secondary:hover {
background-color: #e2e8f0; background-color: #e2e8f0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
} }

View File

@ -4,7 +4,6 @@ export const ImageGalleryAPI = {
ImagesGet: (projectId, filter) => { ImagesGet: (projectId, filter) => {
const payloadJsonString = JSON.stringify(filter); const payloadJsonString = JSON.stringify(filter);
console.log("Applying filters with payload JSON string:", payloadJsonString);
return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}`) return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}`)
}, },
} }