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 "./ImageGallery.css";
import { ImageGalleryAPI } from "./ImageGalleryAPI";
@ -6,12 +7,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
const ImageGallery = () => {
const [images, setImages] = useState([]);
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
const { openModal } = useModal();
const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD');
const [selectedFilters, setSelectedFilters] = useState({
building: [],
floor: [],
@ -95,7 +99,7 @@ const ImageGallery = () => {
const getUniqueValuesWithIds = useCallback(
(idKey, nameKey) => {
const uniqueMap = new Map();
images.forEach(img => {
images.forEach((img) => {
if (img[idKey] && img[nameKey]) {
uniqueMap.set(img[idKey], img[nameKey]);
}
@ -105,21 +109,20 @@ const ImageGallery = () => {
[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);
}
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]
);
}
});
return Array.from(uniqueUsersMap.entries());
}, [images]);
const buildings = getUniqueValuesWithIds("buildingId", "buildingName");
const floors = getUniqueValuesWithIds("floorIds", "floorName");
@ -131,7 +134,7 @@ const ImageGallery = () => {
const toggleFilter = useCallback((type, itemId, itemName) => {
setSelectedFilters((prev) => {
const current = prev[type];
const isSelected = current.some(item => item[0] === itemId);
const isSelected = current.some((item) => item[0] === itemId);
const newArray = isSelected
? current.filter((item) => item[0] !== itemId)
@ -144,10 +147,11 @@ const ImageGallery = () => {
});
}, []);
const handleDateChange = useCallback((type, date) => {
const setDateRange = useCallback(({ startDate, endDate }) => {
setSelectedFilters((prev) => ({
...prev,
[type]: date,
startDate: startDate || "",
endDate: endDate || "",
}));
}, []);
@ -160,12 +164,30 @@ 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 > 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,
};
@ -173,7 +195,6 @@ const ImageGallery = () => {
setIsFilterPanelOpen(false);
}, [selectedFilters]);
const handleClearAllFilters = useCallback(() => {
const initialStateSelected = {
building: [],
@ -202,32 +223,41 @@ const ImageGallery = () => {
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 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 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));
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;
}
);
return isWithinDateRange && passesCategoryFilters;
});
const imagesByActivityUser = {};
filteredImages.forEach((img) => {
const userName = `${img.uploadedBy?.firstName || ""} ${img.uploadedBy?.lastName || ""
}`.trim();
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] = [];
@ -243,62 +273,48 @@ const ImageGallery = () => {
}, []);
const renderFilterCategory = (label, items, type) => (
<div className={`dropdown ${collapsedFilters[type] ? 'collapsed' : ''}`}>
<div className={`dropdown ${collapsedFilters[type] ? "collapsed" : ""}`}>
<div className="dropdown-header" onClick={() => toggleCollapse(type)}>
<strong>{label}</strong>
<div className="header-controls">
{type === 'dateRange' && (selectedFilters.startDate || selectedFilters.endDate) && (
<button
className="clear-button"
onClick={(e) => {
e.stopPropagation();
setSelectedFilters(prev => ({ ...prev, startDate: "", endDate: "" }));
}}
>
Clear
</button>
)}
{type !== 'dateRange' && selectedFilters[type] && selectedFilters[type].length > 0 && (
<button
className="clear-button"
onClick={(e) => {
e.stopPropagation();
setSelectedFilters((prev) => ({ ...prev, [type]: [] }));
}}
>
Clear
</button>
)}
{type === "dateRange" && (selectedFilters.startDate || selectedFilters.endDate) && (
<button
className="clear-button"
onClick={(e) => {
e.stopPropagation();
setSelectedFilters((prev) => ({ ...prev, startDate: "", endDate: "" }));
}}
>
Clear
</button>
)}
{type !== "dateRange" &&
selectedFilters[type] &&
selectedFilters[type].length > 0 && (
<button
className="clear-button"
onClick={(e) => {
e.stopPropagation();
setSelectedFilters((prev) => ({ ...prev, [type]: [] }));
}}
>
Clear
</button>
)}
</div>
</div>
{!collapsedFilters[type] && (
<div className="dropdown-content">
{type === 'dateRange' ? (
<div className="date-range-inputs">
<label>
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>
{type === "dateRange" ? (
// The DateRangePicker will be rendered outside this function, at the end of the filter panel
null
) : (
items.map((item) => {
const itemId = item[0];
const itemName = item[1];
const isChecked = selectedFilters[type].some(selectedItem => selectedItem[0] === itemId);
const isChecked = selectedFilters[type].some(
(selectedItem) => selectedItem[0] === itemId
);
return (
<label key={itemId}>
@ -328,8 +344,7 @@ const ImageGallery = () => {
) : 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 { buildingName, floorName, uploadedAt, workCategoryName } = imgs[0];
const date = moment(uploadedAt).format("YYYY-MM-DD");
const time = moment(uploadedAt).format("hh:mm A");
@ -381,18 +396,16 @@ const ImageGallery = () => {
ref={(el) => (imageGroupRefs.current[key] = el)}
>
{imgs.map((img, idx) => {
const hoverDate = moment(img.uploadedAt).format(
"YYYY-MM-DD"
);
const hoverTime = moment(img.uploadedAt).format(
"hh:mm A"
);
const hoverDate = moment(img.uploadedAt).format("YYYY-MM-DD");
const hoverTime = moment(img.uploadedAt).format("hh:mm A");
return (
<div
key={img.imageUrl}
className="image-card"
onClick={() => openModal(<ImagePop images={imgs} initialIndex={idx} />)}
onClick={() =>
openModal(<ImagePop images={imgs} initialIndex={idx} />)
}
onMouseEnter={() => setHoveredImage(img)}
onMouseLeave={() => setHoveredImage(null)}
>
@ -408,8 +421,7 @@ const ImageGallery = () => {
<strong>Time:</strong> {hoverTime}
</p>
<p>
<strong>Activity:</strong>{" "}
{img.activityName}
<strong>Activity:</strong> {img.activityName}
</p>
</div>
)}
@ -450,13 +462,42 @@ const ImageGallery = () => {
)}
</button>
<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("Floor", floors, "floor")}
{renderFilterCategory("Work Area", workAreas, "workArea")}
{renderFilterCategory("Activity", activities, "activity")}
{renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")}
{renderFilterCategory("Work Category", workCategories, "workCategory")}
{/* DateRangePicker at the end */}
<div className="filter-actions">
<button className="btn btn-secondary btn-xs " onClick={handleClearAllFilters}>

View File

@ -1,3 +1,4 @@
/* ImageGallery.css */
.gallery-container {
display: grid;
grid-template-columns: 1fr 50px;
@ -66,7 +67,8 @@
justify-content: center;
align-items: center;
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;
top: 0;
right: 0;
@ -76,7 +78,7 @@
}
.gallery-container.filter-panel-open .filter-button {
width: calc(100% - 16px);
width: calc(100% - 1px);
height: auto;
padding: 8px 12px;
border-radius: 6px 6px 0 0;
@ -114,21 +116,18 @@
overflow-y: hidden;
opacity: 0;
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 {
max-height: calc(100% - 40px);
max-height: calc(100% - 0px);
opacity: 1;
transform: translateY(0);
overflow-y: auto;
/* padding-bottom: 8px; */
/* Adjust padding to accommodate the new buttons */
padding-bottom: -1px; /* Enough space for buttons + some padding */
padding-bottom: 0px; /* Adjusted padding-bottom */
}
.dropdown {
transition: all 0.3s ease-in-out;
}
@ -139,8 +138,8 @@
box-shadow: none;
padding: 4px 10px;
border-radius: 0 0 4px 4px;
max-height: 150px;
overflow-y: auto;
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;
}
@ -169,6 +168,17 @@
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 {
display: flex;
align-items: center;
@ -208,7 +218,7 @@
}
.dropdown-content input[type="checkbox"]:checked::after {
content: '✔';
content: "✔";
font-size: 10px;
color: white;
position: absolute;
@ -499,47 +509,45 @@ hr {
}
}
/* New styles for filter action buttons */
.filter-actions {
display: flex;
justify-content: space-between;
margin-top: auto; /* Pushes buttons to the bottom */
margin-top: auto;
padding-top: 10px;
border-top: 1px solid #e5e7eb;
background-color: #fff;
position: sticky;
bottom: 0;
z-index: 10;
padding-bottom: 0px;
padding-bottom: 5px;
}
.apply-filters-button,
.clear-all-button {
.btn {
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 */
flex: 1;
margin: 0 4px;
}
.apply-filters-button {
/* background-color: #6366f1;
color: white; */
.btn-primary {
background-color: #6366f1;
color: white;
border: none;
}
.apply-filters-button:hover {
.btn-primary:hover {
background-color: #4f46e5;
/* box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); */
}
.clear-all-button {
.btn-secondary {
border: 1px solid #cbd5e1;
background-color: #fff;
color: #333;
}
.clear-all-button:hover {
.btn-secondary:hover {
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) => {
const payloadJsonString = JSON.stringify(filter);
console.log("Applying filters with payload JSON string:", payloadJsonString);
return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}`)
},
}