Changes in UI of Filter deisgn.
This commit is contained in:
parent
9d5c33eeff
commit
24a412a289
@ -11,18 +11,15 @@ import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import ImageGalleryListView from "../../components/ImageGallery/ImageGalleryListView";
|
||||
import ImageGalleryFilters from "../../components/ImageGallery/ImageGalleryFilters";
|
||||
import "../../components/ImageGallery/ImageGallery.css";
|
||||
|
||||
// --- NEW IMPORTS ---
|
||||
import { useFab } from "../../Context/FabContext";
|
||||
|
||||
const SCROLL_THRESHOLD = 5;
|
||||
|
||||
const ImageGalleryPage = () => {
|
||||
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||
const dispatch = useDispatch();
|
||||
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||
const { projectNames } = useProjectName();
|
||||
const loaderRef = useRef(null);
|
||||
const { openModal } = useModal();
|
||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||
|
||||
// Auto-select first project if none selected
|
||||
useEffect(() => {
|
||||
@ -31,14 +28,14 @@ const ImageGalleryPage = () => {
|
||||
}
|
||||
}, [selectedProjectId, projectNames, dispatch]);
|
||||
|
||||
// --- Filters ---
|
||||
// --- Filters state ---
|
||||
const [appliedFilters, setAppliedFilters] = useState({
|
||||
buildingIds: null,
|
||||
floorIds: null,
|
||||
activityIds: null,
|
||||
uploadedByIds: null,
|
||||
workCategoryIds: null,
|
||||
workAreaIds: null,
|
||||
buildingIds: [],
|
||||
floorIds: [],
|
||||
activityIds: [],
|
||||
uploadedByIds: [],
|
||||
workCategoryIds: [],
|
||||
workAreaIds: [],
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
});
|
||||
@ -54,130 +51,201 @@ const ImageGalleryPage = () => {
|
||||
|
||||
const images = data?.pages.flatMap((page) => page.data) || [];
|
||||
|
||||
// --- Utility: derive filter options ---
|
||||
const getUniqueValues = useCallback(
|
||||
(idKey, nameKey) => {
|
||||
const m = new Map();
|
||||
images.forEach((batch) => {
|
||||
const id = idKey === "floorIds" ? batch.floorIds : batch[idKey];
|
||||
if (id && batch[nameKey] && !m.has(id)) {
|
||||
m.set(id, batch[nameKey]);
|
||||
}
|
||||
});
|
||||
return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1]));
|
||||
},
|
||||
[images]
|
||||
);
|
||||
// --- Utility: store mappings independent of images ---
|
||||
const [labelMaps, setLabelMaps] = useState({
|
||||
buildings: new Map(),
|
||||
floors: new Map(),
|
||||
activities: new Map(),
|
||||
workAreas: new Map(),
|
||||
workCategories: new Map(),
|
||||
uploadedByUsers: new Map(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const buildingsMap = new Map(labelMaps.buildings);
|
||||
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) => {
|
||||
batch.documents.forEach((doc) => {
|
||||
const name = `${doc.uploadedBy?.firstName || ""} ${
|
||||
doc.uploadedBy?.lastName || ""
|
||||
}`.trim();
|
||||
if (doc.uploadedBy?.id && name && !m.has(doc.uploadedBy.id)) {
|
||||
m.set(doc.uploadedBy.id, name);
|
||||
}
|
||||
if (batch.buildingId && batch.buildingName) buildingsMap.set(batch.buildingId, batch.buildingName);
|
||||
if (batch.floorIds && batch.floorName) floorsMap.set(batch.floorIds, batch.floorName);
|
||||
if (batch.activityId && batch.activityName) activitiesMap.set(batch.activityId, batch.activityName);
|
||||
if (batch.workAreaId && batch.workAreaName) workAreasMap.set(batch.workAreaId, batch.workAreaName);
|
||||
if (batch.workCategoryId && batch.workCategoryName) workCategoriesMap.set(batch.workCategoryId, batch.workCategoryName);
|
||||
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]);
|
||||
|
||||
const buildings = getUniqueValues("buildingId", "buildingName");
|
||||
const floors = getUniqueValues("floorIds", "floorName");
|
||||
const activities = getUniqueValues("activityId", "activityName");
|
||||
const workAreas = getUniqueValues("workAreaId", "workAreaName");
|
||||
const workCategories = getUniqueValues("workCategoryId", "workCategoryName");
|
||||
const uploadedByUsers = getUploadedBy();
|
||||
// --- Apply filters ---
|
||||
const handleApplyFilters = useCallback((values) => setAppliedFilters(values), []);
|
||||
|
||||
// --- Apply filters callback ---
|
||||
const handleApplyFilters = useCallback((values) => {
|
||||
setAppliedFilters(values);
|
||||
}, []);
|
||||
// --- Remove single filter ---
|
||||
const handleRemoveFilter = (filterKey, valueId) => {
|
||||
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(
|
||||
() => (
|
||||
<ImageGalleryFilters
|
||||
buildings={buildings}
|
||||
floors={floors}
|
||||
activities={activities}
|
||||
workAreas={workAreas}
|
||||
workCategories={workCategories}
|
||||
uploadedByUsers={uploadedByUsers}
|
||||
buildings={[...labelMaps.buildings]}
|
||||
floors={[...labelMaps.floors]}
|
||||
activities={[...labelMaps.activities]}
|
||||
workAreas={[...labelMaps.workAreas]}
|
||||
workCategories={[...labelMaps.workCategories]}
|
||||
uploadedByUsers={[...labelMaps.uploadedByUsers]}
|
||||
appliedFilters={appliedFilters}
|
||||
onApplyFilters={handleApplyFilters}
|
||||
removeBg
|
||||
/>
|
||||
),
|
||||
[
|
||||
buildings,
|
||||
floors,
|
||||
activities,
|
||||
workAreas,
|
||||
workCategories,
|
||||
uploadedByUsers,
|
||||
appliedFilters,
|
||||
handleApplyFilters,
|
||||
]
|
||||
[labelMaps, appliedFilters, handleApplyFilters]
|
||||
);
|
||||
|
||||
// --- Fab Offcanvas Integration ---
|
||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||
// --- Fab Offcanvas ---
|
||||
useEffect(() => {
|
||||
setShowTrigger(true);
|
||||
setOffcanvasContent("Gallery Filters", filterPanelElement);
|
||||
|
||||
return () => {
|
||||
setShowTrigger(false);
|
||||
setOffcanvasContent("", null);
|
||||
};
|
||||
}, [filterPanelElement, setOffcanvasContent, setShowTrigger]);
|
||||
|
||||
// --- Refetch on project or filters ---
|
||||
// --- EventBus ---
|
||||
useEffect(() => {
|
||||
if (selectedProjectId) refetch();
|
||||
}, [selectedProjectId, appliedFilters, refetch]);
|
||||
|
||||
// --- EventBus Refetch ---
|
||||
useEffect(() => {
|
||||
const handler = (data) => {
|
||||
if (data.projectId === selectedProjectId) refetch();
|
||||
};
|
||||
const handler = (data) => { if (data.projectId === selectedProjectId) refetch(); };
|
||||
eventBus.on("image_gallery", handler);
|
||||
return () => eventBus.off("image_gallery", handler);
|
||||
}, [selectedProjectId, refetch]);
|
||||
|
||||
// --- Infinite scroll observer ---
|
||||
// --- Infinite scroll ---
|
||||
useEffect(() => {
|
||||
if (!loaderRef.current) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (
|
||||
entry.isIntersecting &&
|
||||
hasNextPage &&
|
||||
!isFetchingNextPage &&
|
||||
!isLoading
|
||||
) {
|
||||
fetchNextPage();
|
||||
}
|
||||
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage && !isLoading) fetchNextPage();
|
||||
},
|
||||
{ rootMargin: "200px", threshold: 0.1 }
|
||||
);
|
||||
|
||||
observer.observe(loaderRef.current);
|
||||
|
||||
return () => {
|
||||
if (loaderRef.current) {
|
||||
observer.unobserve(loaderRef.current);
|
||||
}
|
||||
};
|
||||
return () => loaderRef.current && observer.unobserve(loaderRef.current);
|
||||
}, [hasNextPage, isFetchingNextPage, isLoading, fetchNextPage]);
|
||||
|
||||
return (
|
||||
<div className="gallery-container container-fluid">
|
||||
<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
|
||||
images={images}
|
||||
isLoading={isLoading}
|
||||
@ -185,7 +253,6 @@ const ImageGalleryPage = () => {
|
||||
hasNextPage={hasNextPage}
|
||||
loaderRef={loaderRef}
|
||||
openModal={openModal}
|
||||
SCROLL_THRESHOLD={SCROLL_THRESHOLD}
|
||||
formatUTCToLocalTime={formatUTCToLocalTime}
|
||||
moment={moment}
|
||||
/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user