Switch time filters to hours-based range
This commit is contained in:
parent
d854da7c1b
commit
bd512ba04d
|
|
@ -409,7 +409,7 @@ export async function fetchSmartFeed(params = {}) {
|
|||
if (typeof params.zoom === "number") qs.set("zoom", String(params.zoom));
|
||||
if (params.category) qs.set("category", String(params.category));
|
||||
if (params.sub_category) qs.set("sub_category", String(params.sub_category));
|
||||
if (params.time_filter) qs.set("time_filter", String(params.time_filter));
|
||||
if (params.time_hours) qs.set("time_hours", String(params.time_hours));
|
||||
|
||||
// NEW: Screen bounds for on-screen prioritization
|
||||
if (params.bounds) {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import React from "react";
|
||||
|
||||
export default function TimeFilterButtons({ active, onSelect }) {
|
||||
export default function TimeFilterButtons({ activeHours, onSelect }) {
|
||||
const buttons = [
|
||||
{ code: "NOW", label: "Now", icon: "fa-solid fa-bolt" },
|
||||
{ code: "TODAY", label: "Today", icon: "fa-solid fa-sun" },
|
||||
{ code: "RECENT", label: "Recent", icon: "fa-solid fa-clock-rotate-left" },
|
||||
{ code: "PAST", label: "Old", icon: "fa-solid fa-hourglass-half" },
|
||||
{ hours: 2, label: "Now", icon: "fa-solid fa-bolt" },
|
||||
{ hours: 24, label: "Today", icon: "fa-solid fa-sun" },
|
||||
{ hours: 72, label: "Recent", icon: "fa-solid fa-clock-rotate-left" },
|
||||
{ hours: 336, label: "Old", icon: "fa-solid fa-hourglass-half" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="sw-icon-column">
|
||||
{buttons.map((btn) => (
|
||||
<button
|
||||
key={btn.code}
|
||||
key={btn.hours}
|
||||
type="button"
|
||||
className={"sw-icon-btn" + (active === btn.code ? " sw-icon-active" : "")}
|
||||
onClick={() => onSelect && onSelect(btn.code)}
|
||||
className={"sw-icon-btn" + (activeHours === btn.hours ? " sw-icon-active" : "")}
|
||||
onClick={() => onSelect && onSelect(btn.hours)}
|
||||
>
|
||||
<div className="sw-icon-circle">
|
||||
<i className={btn.icon} />
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ export default function MapView({
|
|||
useMapCore(theme);
|
||||
|
||||
const [mainFilter, setMainFilter] = useState("All");
|
||||
const TIME_FILTER_KEY = "sociowire:timeFilter";
|
||||
const [timeFilter, setTimeFilter] = useState(() => {
|
||||
const TIME_FILTER_KEY = "sociowire:timeHours";
|
||||
const [timeHours, setTimeHours] = useState(() => {
|
||||
try {
|
||||
const v = localStorage.getItem(TIME_FILTER_KEY);
|
||||
const ok = ["NOW", "TODAY", "RECENT", "PAST"].includes(v);
|
||||
return ok ? v : "PAST";
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) && n > 0 ? n : 336;
|
||||
} catch {
|
||||
return "PAST";
|
||||
return 336;
|
||||
}
|
||||
});
|
||||
const [subFilter, setSubFilter] = useState("All");
|
||||
|
|
@ -55,9 +55,9 @@ export default function MapView({
|
|||
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(TIME_FILTER_KEY, timeFilter);
|
||||
localStorage.setItem(TIME_FILTER_KEY, String(timeHours));
|
||||
} catch {}
|
||||
}, [timeFilter]);
|
||||
}, [timeHours]);
|
||||
|
||||
const stageRef = useRef(null);
|
||||
const [showSubcats, setShowSubcats] = useState(true);
|
||||
|
|
@ -159,7 +159,7 @@ export default function MapView({
|
|||
viewParams,
|
||||
mainFilter,
|
||||
subFilter,
|
||||
timeFilter,
|
||||
timeHours,
|
||||
searchQuery,
|
||||
clusterFocus,
|
||||
mainNewsOnly,
|
||||
|
|
@ -168,7 +168,7 @@ export default function MapView({
|
|||
forceFullPostId,
|
||||
markersRef,
|
||||
expandedElRef,
|
||||
onAutoWidenTimeFilter: (next) => setTimeFilter(next),
|
||||
onAutoWidenTimeFilter: () => setTimeHours(336),
|
||||
onSelectPost,
|
||||
username,
|
||||
});
|
||||
|
|
@ -312,8 +312,8 @@ export default function MapView({
|
|||
}, [subFilter, mainFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
trackEvent("filter_time_change", { filter: timeFilter });
|
||||
}, [timeFilter]);
|
||||
trackEvent("filter_time_change", { hours: timeHours });
|
||||
}, [timeHours]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryPostRef.current) return;
|
||||
|
|
@ -627,7 +627,7 @@ export default function MapView({
|
|||
setSearchQuery("");
|
||||
setMainFilter("All");
|
||||
setSubFilter("All");
|
||||
setTimeFilter("PAST"); // Show all time periods
|
||||
setTimeHours(336); // 2 weeks
|
||||
|
||||
const postId = Number(raw?.post_id ?? raw?.id ?? item?.id ?? 0);
|
||||
const rawUrl = raw?.url || item?.url || "";
|
||||
|
|
@ -748,7 +748,7 @@ export default function MapView({
|
|||
if (q) {
|
||||
setMainFilter("All");
|
||||
setSubFilter("All");
|
||||
setTimeFilter("PAST");
|
||||
setTimeHours(336);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -762,7 +762,7 @@ export default function MapView({
|
|||
if (query.trim()) {
|
||||
setMainFilter("All");
|
||||
setSubFilter("All");
|
||||
setTimeFilter("PAST"); // Show all time periods
|
||||
setTimeHours(336); // 2 weeks
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1254,8 +1254,8 @@ export default function MapView({
|
|||
|
||||
<div className="map-overlay map-overlay-left">
|
||||
<TimeFilterButtons
|
||||
active={timeFilter}
|
||||
onSelect={(code) => setTimeFilter(code)}
|
||||
activeHours={timeHours}
|
||||
onSelect={(hours) => setTimeHours(hours)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -54,14 +54,12 @@ function parseCreatedAt(createdAt) {
|
|||
return Number.isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
export function matchesTimeFilter(createdAt, timeFilter) {
|
||||
if (!timeFilter) return true;
|
||||
if (timeFilter === "PAST") return true;
|
||||
export function matchesTimeFilter(createdAt, timeHours) {
|
||||
const hours = Number(timeHours);
|
||||
if (!Number.isFinite(hours) || hours <= 0) return true;
|
||||
|
||||
const d = parseCreatedAt(createdAt);
|
||||
if (!d) {
|
||||
return timeFilter !== "NOW";
|
||||
}
|
||||
if (!d) return false;
|
||||
|
||||
const now = Date.now();
|
||||
const diffMs = now - d.getTime();
|
||||
|
|
@ -69,20 +67,7 @@ export function matchesTimeFilter(createdAt, timeFilter) {
|
|||
if (diffMs < 0) return false;
|
||||
|
||||
const diffHours = diffMs / 3600000;
|
||||
const diffDays = diffMs / 86400000;
|
||||
|
||||
switch (timeFilter) {
|
||||
case "NOW":
|
||||
return diffHours <= 2;
|
||||
case "TODAY":
|
||||
return diffHours <= 24;
|
||||
case "RECENT":
|
||||
return diffHours <= 72;
|
||||
case "PAST":
|
||||
return diffDays <= 15;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return diffHours <= hours;
|
||||
}
|
||||
|
||||
export function effectivePostTime(post) {
|
||||
|
|
|
|||
|
|
@ -360,7 +360,7 @@ export function usePostsEngine({
|
|||
viewParams,
|
||||
mainFilter,
|
||||
subFilter,
|
||||
timeFilter,
|
||||
timeHours,
|
||||
searchQuery,
|
||||
clusterFocus,
|
||||
mainNewsOnly,
|
||||
|
|
@ -375,7 +375,7 @@ export function usePostsEngine({
|
|||
theme = "blue",
|
||||
}) {
|
||||
const allPostsRef = useRef([]);
|
||||
const lastFetchRef = useRef({ center: null, radiusKm: null, filterKey: "", timeFilter: "", bounds: null });
|
||||
const lastFetchRef = useRef({ center: null, radiusKm: null, filterKey: "", timeHours: 0, bounds: null });
|
||||
const pendingFilterRef = useRef("");
|
||||
const requestSeqRef = useRef(0);
|
||||
const delayedFetchRef = useRef(null);
|
||||
|
|
@ -433,7 +433,7 @@ export function usePostsEngine({
|
|||
useEffect(() => {
|
||||
const map = mapRef.current;
|
||||
replacePoolRef.current = true;
|
||||
pendingFilterRef.current = `${mainFilter}|${subFilter}|${timeFilter}`;
|
||||
pendingFilterRef.current = `${mainFilter}|${subFilter}|${timeHours}`;
|
||||
if (map) {
|
||||
map.__swForceFetchAt = Date.now();
|
||||
setTimeout(() => {
|
||||
|
|
@ -442,7 +442,7 @@ export function usePostsEngine({
|
|||
} catch {}
|
||||
}, 60);
|
||||
}
|
||||
}, [mainFilter, subFilter, timeFilter, mapRef]);
|
||||
}, [mainFilter, subFilter, timeHours, mapRef]);
|
||||
|
||||
const priorityOpts = {
|
||||
maxFullCards: 15,
|
||||
|
|
@ -861,7 +861,7 @@ export function usePostsEngine({
|
|||
if (pc !== catCode) return false;
|
||||
}
|
||||
if (!matchesSubFilter(p, subFilter)) return false;
|
||||
const skipTimeFilter = lastFetchRef.current?.timeFilter === tf;
|
||||
const skipTimeFilter = lastFetchRef.current?.timeHours === tf;
|
||||
if (!skipTimeFilter && !matchesTimeFilter(effectivePostTime(p), tf)) return false;
|
||||
if (!searchResultsRef.current && !matchesSearch(p, searchQuery)) return false;
|
||||
return true;
|
||||
|
|
@ -932,8 +932,8 @@ export function usePostsEngine({
|
|||
// On filter/search changes: recompute + sync (NO clear-all rebuild)
|
||||
useEffect(() => {
|
||||
if (pendingFilterRef.current) return;
|
||||
refreshVisibleAndMarkers(timeFilter);
|
||||
}, [timeFilter, mainFilter, subFilter, searchQuery, clusterFocus, refreshVisibleAndMarkers]);
|
||||
refreshVisibleAndMarkers(timeHours);
|
||||
}, [timeHours, mainFilter, subFilter, searchQuery, clusterFocus, refreshVisibleAndMarkers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clustersEnabled) return;
|
||||
|
|
@ -941,14 +941,14 @@ export function usePostsEngine({
|
|||
if (Array.isArray(clusterFocus.posts) && clusterFocus.posts.length > 0) {
|
||||
allPostsRef.current = clusterFocus.posts;
|
||||
}
|
||||
refreshVisibleAndMarkers(timeFilter, true);
|
||||
}, [clusterFocus, timeFilter, refreshVisibleAndMarkers, clustersEnabled]);
|
||||
refreshVisibleAndMarkers(timeHours, true);
|
||||
}, [clusterFocus, timeHours, refreshVisibleAndMarkers, clustersEnabled]);
|
||||
|
||||
// On view change: re-filter markers/cards to current bounds even without fetch
|
||||
useEffect(() => {
|
||||
if (pendingFilterRef.current) return;
|
||||
refreshVisibleAndMarkers(timeFilter);
|
||||
}, [viewParams, refreshVisibleAndMarkers, timeFilter]);
|
||||
refreshVisibleAndMarkers(timeHours);
|
||||
}, [viewParams, refreshVisibleAndMarkers, timeHours]);
|
||||
|
||||
// Fetch on view change (moveend)
|
||||
useEffect(() => {
|
||||
|
|
@ -985,7 +985,7 @@ export function usePostsEngine({
|
|||
const [lng, lat] = resolvedView.center;
|
||||
const radiusKm = resolvedView.radiusKm || 750;
|
||||
const filterKey = `${mainFilter}|${subFilter}`;
|
||||
const currentKey = `${filterKey}|${timeFilter}`;
|
||||
const currentKey = `${filterKey}|${timeHours}`;
|
||||
const reqId = ++requestSeqRef.current;
|
||||
let bounds = null;
|
||||
try {
|
||||
|
|
@ -1009,7 +1009,7 @@ export function usePostsEngine({
|
|||
mapObj.__swForceFetchAt = 0;
|
||||
}
|
||||
|
||||
if (!forceFetch && last.center && last.filterKey === filterKey && last.timeFilter === timeFilter) {
|
||||
if (!forceFetch && last.center && last.filterKey === filterKey && last.timeHours === timeHours) {
|
||||
const [lastLng, lastLat] = last.center;
|
||||
const distKm = haversineKm(lastLat, lastLng, lat, lng);
|
||||
|
||||
|
|
@ -1061,7 +1061,7 @@ export function usePostsEngine({
|
|||
zoom,
|
||||
bounds,
|
||||
username: username || undefined,
|
||||
time_filter: timeFilter || undefined,
|
||||
time_hours: timeHours || undefined,
|
||||
category: catCode || undefined,
|
||||
sub_category: subCatParam || undefined,
|
||||
});
|
||||
|
|
@ -1080,7 +1080,7 @@ export function usePostsEngine({
|
|||
const normalized = normalizePostId(p);
|
||||
const key = getPostKey(normalized);
|
||||
if (!key) continue;
|
||||
if (timeFilter) normalized._swTimeFilter = timeFilter;
|
||||
if (timeHours) normalized._swTimeHours = timeHours;
|
||||
const prior = byId.get(key);
|
||||
if (prior) {
|
||||
byId.set(key, { ...prior, ...normalized });
|
||||
|
|
@ -1092,14 +1092,14 @@ export function usePostsEngine({
|
|||
|
||||
allPostsRef.current = prunePosts(Array.from(byId.values()), [lng, lat], MAX_POOL_POSTS, bounds);
|
||||
replacePoolRef.current = false;
|
||||
lastFetchRef.current = { center: [...resolvedView.center], radiusKm, filterKey, timeFilter, bounds };
|
||||
lastFetchRef.current = { center: [...resolvedView.center], radiusKm, filterKey, timeHours, bounds };
|
||||
if (pendingFilterRef.current === currentKey) {
|
||||
pendingFilterRef.current = "";
|
||||
}
|
||||
|
||||
// Smooth update: sync markers/clusters + wall without clearing everything
|
||||
refreshVisibleAndMarkers(timeFilter);
|
||||
const visible = computeVisible(timeFilter);
|
||||
refreshVisibleAndMarkers(timeHours);
|
||||
const visible = computeVisible(timeHours);
|
||||
const now = Date.now();
|
||||
if (now - analyticsDebounceRef.current > 4000) {
|
||||
analyticsDebounceRef.current = now;
|
||||
|
|
@ -1108,20 +1108,20 @@ export function usePostsEngine({
|
|||
radius_km: Math.round(radiusKm || 0),
|
||||
zoom: typeof zoom === "number" ? Math.round(zoom * 10) / 10 : undefined,
|
||||
filter: filterKey,
|
||||
time_filter: timeFilter || "",
|
||||
time_hours: timeHours || 0,
|
||||
});
|
||||
}
|
||||
if (
|
||||
!autoWidenRef.current &&
|
||||
timeFilter &&
|
||||
timeFilter !== "PAST" &&
|
||||
timeHours &&
|
||||
timeHours > 0 &&
|
||||
Array.isArray(newPosts) &&
|
||||
newPosts.length > 0 &&
|
||||
visible.length === 0
|
||||
) {
|
||||
autoWidenRef.current = true;
|
||||
if (typeof onAutoWidenTimeFilter === "function") {
|
||||
onAutoWidenTimeFilter("PAST");
|
||||
onAutoWidenTimeFilter(336);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1167,7 +1167,7 @@ export function usePostsEngine({
|
|||
delayedFetchRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [viewParams, mapReady, mainFilter, subFilter, mapRef, timeFilter, expandedElRef, refreshVisibleAndMarkers, computeVisible, onAutoWidenTimeFilter, syncMarkers, username]);
|
||||
}, [viewParams, mapReady, mainFilter, subFilter, mapRef, timeHours, expandedElRef, refreshVisibleAndMarkers, computeVisible, onAutoWidenTimeFilter, syncMarkers, username]);
|
||||
|
||||
useEffect(() => {
|
||||
const q = (searchQuery || "").trim();
|
||||
|
|
@ -1215,7 +1215,7 @@ export function usePostsEngine({
|
|||
} catch {}
|
||||
allPostsRef.current = prunePosts(normalized, [lon, lat], MAX_POOL_POSTS, bounds);
|
||||
searchResultsRef.current = true;
|
||||
refreshVisibleAndMarkers(timeFilter);
|
||||
refreshVisibleAndMarkers(timeHours);
|
||||
trackEvent("search_filter_apply", {
|
||||
query_len: q.length,
|
||||
results: posts.length,
|
||||
|
|
@ -1228,7 +1228,7 @@ export function usePostsEngine({
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [searchQuery, viewParams, mapRef, refreshVisibleAndMarkers, timeFilter]);
|
||||
}, [searchQuery, viewParams, mapRef, refreshVisibleAndMarkers, timeHours]);
|
||||
|
||||
useEffect(() => {
|
||||
if ((searchQuery || "").trim()) return;
|
||||
|
|
@ -1243,13 +1243,13 @@ export function usePostsEngine({
|
|||
if (!clusterModeRef.current) return;
|
||||
ensureClusterLayers(map);
|
||||
setClusterVisibility(map, true);
|
||||
const { clusterPosts } = splitForClusters(computeVisible(timeFilter));
|
||||
const { clusterPosts } = splitForClusters(computeVisible(timeHours));
|
||||
const clusterRemainder = clusterPosts.slice(SIMPLE_MARKER_LIMIT);
|
||||
updateClusterData(map, clusterRemainder);
|
||||
};
|
||||
map.on("styledata", handleStyle);
|
||||
return () => map.off("styledata", handleStyle);
|
||||
}, [mapRef, ensureClusterLayers, setClusterVisibility, updateClusterData, computeVisible, timeFilter, splitForClusters, clustersEnabled]);
|
||||
}, [mapRef, ensureClusterLayers, setClusterVisibility, updateClusterData, computeVisible, timeHours, splitForClusters, clustersEnabled]);
|
||||
|
||||
const handleIncomingPost = useCallback(
|
||||
(p) => {
|
||||
|
|
@ -1278,11 +1278,11 @@ export function usePostsEngine({
|
|||
if (pc !== catCode) return;
|
||||
}
|
||||
if (!matchesSubFilter(p, subFilter)) return;
|
||||
if (!matchesTimeFilter(effectivePostTime(p), timeFilter)) return;
|
||||
if (!matchesTimeFilter(effectivePostTime(p), timeHours)) return;
|
||||
if (!matchesSearch(p, searchQuery)) return;
|
||||
|
||||
if (clusterModeRef.current) {
|
||||
refreshVisibleAndMarkers(timeFilter);
|
||||
refreshVisibleAndMarkers(timeHours);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1295,7 +1295,7 @@ export function usePostsEngine({
|
|||
|
||||
setVisiblePosts((current) => [normalized, ...current]);
|
||||
},
|
||||
[mainFilter, subFilter, timeFilter, searchQuery, mapRef, markersRef, expandedElRef, theme, refreshVisibleAndMarkers]
|
||||
[mainFilter, subFilter, timeHours, searchQuery, mapRef, markersRef, expandedElRef, theme, refreshVisibleAndMarkers]
|
||||
);
|
||||
|
||||
const handlePostUpdate = useCallback(
|
||||
|
|
@ -1338,7 +1338,7 @@ export function usePostsEngine({
|
|||
}
|
||||
if (changed) {
|
||||
allPostsRef.current = updated;
|
||||
refreshVisibleAndMarkers(timeFilter, true);
|
||||
refreshVisibleAndMarkers(timeHours, true);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -1381,7 +1381,7 @@ export function usePostsEngine({
|
|||
}
|
||||
} catch {}
|
||||
},
|
||||
[mapRef, refreshVisibleAndMarkers, timeFilter]
|
||||
[mapRef, refreshVisibleAndMarkers, timeHours]
|
||||
);
|
||||
|
||||
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost, handlePostUpdate };
|
||||
|
|
|
|||
Loading…
Reference in New Issue