Switch time filters to hours-based range

This commit is contained in:
Your Name 2025-12-30 19:54:21 -05:00
parent d854da7c1b
commit bd512ba04d
5 changed files with 62 additions and 77 deletions

View File

@ -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) {

View File

@ -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} />

View File

@ -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>

View File

@ -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) {

View File

@ -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 };