Changes in UI of Filter deisgn.

This commit is contained in:
Kartik Sharma 2025-09-18 18:43:30 +05:30
parent 9d5c33eeff
commit 24a412a289

View File

@ -11,18 +11,15 @@ import { setProjectId } from "../../slices/localVariablesSlice";
import ImageGalleryListView from "../../components/ImageGallery/ImageGalleryListView"; import ImageGalleryListView from "../../components/ImageGallery/ImageGalleryListView";
import ImageGalleryFilters from "../../components/ImageGallery/ImageGalleryFilters"; import ImageGalleryFilters from "../../components/ImageGallery/ImageGalleryFilters";
import "../../components/ImageGallery/ImageGallery.css"; import "../../components/ImageGallery/ImageGallery.css";
// --- NEW IMPORTS ---
import { useFab } from "../../Context/FabContext"; import { useFab } from "../../Context/FabContext";
const SCROLL_THRESHOLD = 5;
const ImageGalleryPage = () => { const ImageGalleryPage = () => {
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
const dispatch = useDispatch(); const dispatch = useDispatch();
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
const { projectNames } = useProjectName(); const { projectNames } = useProjectName();
const loaderRef = useRef(null); const loaderRef = useRef(null);
const { openModal } = useModal(); const { openModal } = useModal();
const { setOffcanvasContent, setShowTrigger } = useFab();
// Auto-select first project if none selected // Auto-select first project if none selected
useEffect(() => { useEffect(() => {
@ -31,14 +28,14 @@ const ImageGalleryPage = () => {
} }
}, [selectedProjectId, projectNames, dispatch]); }, [selectedProjectId, projectNames, dispatch]);
// --- Filters --- // --- Filters state ---
const [appliedFilters, setAppliedFilters] = useState({ const [appliedFilters, setAppliedFilters] = useState({
buildingIds: null, buildingIds: [],
floorIds: null, floorIds: [],
activityIds: null, activityIds: [],
uploadedByIds: null, uploadedByIds: [],
workCategoryIds: null, workCategoryIds: [],
workAreaIds: null, workAreaIds: [],
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
@ -54,130 +51,201 @@ const ImageGalleryPage = () => {
const images = data?.pages.flatMap((page) => page.data) || []; const images = data?.pages.flatMap((page) => page.data) || [];
// --- Utility: derive filter options --- // --- Utility: store mappings independent of images ---
const getUniqueValues = useCallback( const [labelMaps, setLabelMaps] = useState({
(idKey, nameKey) => { buildings: new Map(),
const m = new Map(); floors: new Map(),
images.forEach((batch) => { activities: new Map(),
const id = idKey === "floorIds" ? batch.floorIds : batch[idKey]; workAreas: new Map(),
if (id && batch[nameKey] && !m.has(id)) { workCategories: new Map(),
m.set(id, batch[nameKey]); uploadedByUsers: new Map(),
} });
});
return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1])); useEffect(() => {
}, const buildingsMap = new Map(labelMaps.buildings);
[images] const floorsMap = new Map(labelMaps.floors);
); const activitiesMap = new Map(labelMaps.activities);
const workAreasMap = new Map(labelMaps.workAreas);
const workCategoriesMap = new Map(labelMaps.workCategories);
const uploadedByMap = new Map(labelMaps.uploadedByUsers);
const getUploadedBy = useCallback(() => {
const m = new Map();
images.forEach((batch) => { images.forEach((batch) => {
batch.documents.forEach((doc) => { if (batch.buildingId && batch.buildingName) buildingsMap.set(batch.buildingId, batch.buildingName);
const name = `${doc.uploadedBy?.firstName || ""} ${ if (batch.floorIds && batch.floorName) floorsMap.set(batch.floorIds, batch.floorName);
doc.uploadedBy?.lastName || "" if (batch.activityId && batch.activityName) activitiesMap.set(batch.activityId, batch.activityName);
}`.trim(); if (batch.workAreaId && batch.workAreaName) workAreasMap.set(batch.workAreaId, batch.workAreaName);
if (doc.uploadedBy?.id && name && !m.has(doc.uploadedBy.id)) { if (batch.workCategoryId && batch.workCategoryName) workCategoriesMap.set(batch.workCategoryId, batch.workCategoryName);
m.set(doc.uploadedBy.id, name); batch.documents?.forEach((doc) => {
} const name = `${doc.uploadedBy?.firstName || ""} ${doc.uploadedBy?.lastName || ""}`.trim();
if (doc.uploadedBy?.id && name) uploadedByMap.set(doc.uploadedBy.id, name);
}); });
}); });
return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1]));
setLabelMaps({
buildings: buildingsMap,
floors: floorsMap,
activities: activitiesMap,
workAreas: workAreasMap,
workCategories: workCategoriesMap,
uploadedByUsers: uploadedByMap,
});
}, [images]); }, [images]);
const buildings = getUniqueValues("buildingId", "buildingName"); // --- Apply filters ---
const floors = getUniqueValues("floorIds", "floorName"); const handleApplyFilters = useCallback((values) => setAppliedFilters(values), []);
const activities = getUniqueValues("activityId", "activityName");
const workAreas = getUniqueValues("workAreaId", "workAreaName");
const workCategories = getUniqueValues("workCategoryId", "workCategoryName");
const uploadedByUsers = getUploadedBy();
// --- Apply filters callback --- // --- Remove single filter ---
const handleApplyFilters = useCallback((values) => { const handleRemoveFilter = (filterKey, valueId) => {
setAppliedFilters(values); setAppliedFilters((prev) => {
}, []); const updated = { ...prev };
if (Array.isArray(updated[filterKey])) {
updated[filterKey] = updated[filterKey].filter((id) => id !== valueId);
} else if (filterKey === "startDate" || filterKey === "endDate") {
updated[filterKey] = null;
}
return updated;
});
};
// --- Filter Panel Memoization --- // --- Chips ---
const appliedFiltersChips = useMemo(() => {
const chips = [];
const { buildings, floors, activities, workAreas, workCategories, uploadedByUsers } = labelMaps;
appliedFilters.buildingIds?.forEach((id) =>
chips.push({ label: "Building", value: buildings.get(id) || id, key: "buildingIds", id })
);
appliedFilters.floorIds?.forEach((id) =>
chips.push({ label: "Floor", value: floors.get(id) || id, key: "floorIds", id })
);
appliedFilters.workAreaIds?.forEach((id) =>
chips.push({ label: "Work Area", value: workAreas.get(id) || id, key: "workAreaIds", id })
);
appliedFilters.activityIds?.forEach((id) =>
chips.push({ label: "Activity", value: activities.get(id) || id, key: "activityIds", id })
);
appliedFilters.uploadedByIds?.forEach((id) =>
chips.push({ label: "Uploaded By", value: uploadedByUsers.get(id) || id, key: "uploadedByIds", id })
);
appliedFilters.workCategoryIds?.forEach((id) =>
chips.push({ label: "Work Category", value: workCategories.get(id) || id, key: "workCategoryIds", id })
);
if (appliedFilters.startDate || appliedFilters.endDate) {
const start = appliedFilters.startDate ? moment(appliedFilters.startDate).format("DD MMM, YYYY") : "";
const end = appliedFilters.endDate ? moment(appliedFilters.endDate).format("DD MMM, YYYY") : "";
chips.push({ label: "Date Range", value: `${start} - ${end}`, key: "dateRange" });
}
return chips;
}, [appliedFilters, labelMaps]);
// --- Refetch on filter change ---
useEffect(() => { refetch(); }, [appliedFilters, refetch]);
// --- Filter Panel ---
const filterPanelElement = useMemo( const filterPanelElement = useMemo(
() => ( () => (
<ImageGalleryFilters <ImageGalleryFilters
buildings={buildings} buildings={[...labelMaps.buildings]}
floors={floors} floors={[...labelMaps.floors]}
activities={activities} activities={[...labelMaps.activities]}
workAreas={workAreas} workAreas={[...labelMaps.workAreas]}
workCategories={workCategories} workCategories={[...labelMaps.workCategories]}
uploadedByUsers={uploadedByUsers} uploadedByUsers={[...labelMaps.uploadedByUsers]}
appliedFilters={appliedFilters} appliedFilters={appliedFilters}
onApplyFilters={handleApplyFilters} onApplyFilters={handleApplyFilters}
removeBg
/> />
), ),
[ [labelMaps, appliedFilters, handleApplyFilters]
buildings,
floors,
activities,
workAreas,
workCategories,
uploadedByUsers,
appliedFilters,
handleApplyFilters,
]
); );
// --- Fab Offcanvas Integration --- // --- Fab Offcanvas ---
const { setOffcanvasContent, setShowTrigger } = useFab();
useEffect(() => { useEffect(() => {
setShowTrigger(true); setShowTrigger(true);
setOffcanvasContent("Gallery Filters", filterPanelElement); setOffcanvasContent("Gallery Filters", filterPanelElement);
return () => { return () => {
setShowTrigger(false); setShowTrigger(false);
setOffcanvasContent("", null); setOffcanvasContent("", null);
}; };
}, [filterPanelElement, setOffcanvasContent, setShowTrigger]); }, [filterPanelElement, setOffcanvasContent, setShowTrigger]);
// --- Refetch on project or filters --- // --- EventBus ---
useEffect(() => { useEffect(() => {
if (selectedProjectId) refetch(); const handler = (data) => { if (data.projectId === selectedProjectId) refetch(); };
}, [selectedProjectId, appliedFilters, refetch]);
// --- EventBus Refetch ---
useEffect(() => {
const handler = (data) => {
if (data.projectId === selectedProjectId) refetch();
};
eventBus.on("image_gallery", handler); eventBus.on("image_gallery", handler);
return () => eventBus.off("image_gallery", handler); return () => eventBus.off("image_gallery", handler);
}, [selectedProjectId, refetch]); }, [selectedProjectId, refetch]);
// --- Infinite scroll observer --- // --- Infinite scroll ---
useEffect(() => { useEffect(() => {
if (!loaderRef.current) return; if (!loaderRef.current) return;
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
([entry]) => { ([entry]) => {
if ( if (entry.isIntersecting && hasNextPage && !isFetchingNextPage && !isLoading) fetchNextPage();
entry.isIntersecting &&
hasNextPage &&
!isFetchingNextPage &&
!isLoading
) {
fetchNextPage();
}
}, },
{ rootMargin: "200px", threshold: 0.1 } { rootMargin: "200px", threshold: 0.1 }
); );
observer.observe(loaderRef.current); observer.observe(loaderRef.current);
return () => loaderRef.current && observer.unobserve(loaderRef.current);
return () => {
if (loaderRef.current) {
observer.unobserve(loaderRef.current);
}
};
}, [hasNextPage, isFetchingNextPage, isLoading, fetchNextPage]); }, [hasNextPage, isFetchingNextPage, isLoading, fetchNextPage]);
return ( return (
<div className="gallery-container container-fluid"> <div className="gallery-container container-fluid">
<Breadcrumb data={[{ label: "Home", link: "/" }, { label: "Gallery" }]} /> <Breadcrumb data={[{ label: "Home", link: "/" }, { label: "Gallery" }]} />
{appliedFiltersChips.length > 0 && (
<div className="mb-3 text-start d-flex flex-wrap align-items-center gap-2">
<strong className="me-2 fs-6 ms-1">Filters:</strong>
{/* Group chips by label */}
{["Building", "Floor", "Work Area", "Activity", "Uploaded By", "Work Category"].map((label) => {
const chipsForLabel = appliedFiltersChips.filter((chip) => chip.label === label);
if (chipsForLabel.length === 0) return null;
return (
<span key={label} className="d-flex align-items-center px-2 py-1 rounded" style={{ background: "transparent" }}>
<strong className="me-1">{label} :</strong>
{chipsForLabel.map((chip, idx) => (
<span
key={chip.id}
className="d-flex align-items-center bg-label-secondary px-2 py-1 rounded me-1"
>
{chip.value}
<button
type="button"
className="btn-close btn-close-white btn-sm ms-1"
aria-label="Remove"
onClick={() => handleRemoveFilter(chip.key, chip.id)}
/>
</span>
))}
</span>
);
})}
{/* Date Range */}
{appliedFiltersChips.some((chip) => chip.label === "Date Range") && (
<span className="d-flex align-items-center px-2 py-1 rounded bg-label-secondary">
<strong className="me-1">Date Range :</strong>
{appliedFiltersChips
.filter((chip) => chip.label === "Date Range")
.map((chip, idx) => (
<span key={idx} className="d-flex align-items-center me-1">
{chip.value}
<button
type="button"
className="btn-close btn-close-white btn-sm ms-1"
aria-label="Remove"
onClick={() => handleRemoveFilter(chip.key, chip.id)}
/>
</span>
))}
</span>
)}
</div>
)}
<ImageGalleryListView <ImageGalleryListView
images={images} images={images}
isLoading={isLoading} isLoading={isLoading}
@ -185,7 +253,6 @@ const ImageGalleryPage = () => {
hasNextPage={hasNextPage} hasNextPage={hasNextPage}
loaderRef={loaderRef} loaderRef={loaderRef}
openModal={openModal} openModal={openModal}
SCROLL_THRESHOLD={SCROLL_THRESHOLD}
formatUTCToLocalTime={formatUTCToLocalTime} formatUTCToLocalTime={formatUTCToLocalTime}
moment={moment} moment={moment}
/> />