From 8a3b4c655864537fe47f9beaa88b68acce8b59aa Mon Sep 17 00:00:00 2001 From: ash1shkumar Date: Sat, 6 Jun 2026 01:19:45 +0530 Subject: [PATCH] refactor: optimize feed filtering and pagination flow --- frontend/app/insights/feed/page.tsx | 354 +++++++++++++++++++++------- 1 file changed, 265 insertions(+), 89 deletions(-) diff --git a/frontend/app/insights/feed/page.tsx b/frontend/app/insights/feed/page.tsx index 19ce0b7..03c3876 100644 --- a/frontend/app/insights/feed/page.tsx +++ b/frontend/app/insights/feed/page.tsx @@ -1,12 +1,18 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import FeedHeader, { type FeedFilter } from "@/app/components/Feed-comp/FeedHeader"; -import FeedList, { type FeedActivityItem } from "@/app/components/Feed-comp/FeedList"; +import FeedHeader, { + type FeedFilter, +} from "@/app/components/Feed-comp/FeedHeader"; +import FeedList, { + type FeedActivityItem, +} from "@/app/components/Feed-comp/FeedList"; import FeedMobileNav from "@/app/components/Feed-comp/FeedMobileNav"; import { supabase } from "@/app/lib/supabase"; -const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000"; +const API_URL = + process.env.NEXT_PUBLIC_API_URL || + "http://localhost:5000"; const fallbackItems: FeedActivityItem[] = [ { @@ -14,12 +20,15 @@ const fallbackItems: FeedActivityItem[] = [ type: "code", actor: "Alex Rivera", action: "deployed to production", - title: "Production Environment: v2.4.0-rc1", - body: "Successfully deployed 14 services and updated the global edge configuration.", + title: + "Production Environment: v2.4.0-rc1", + body: + "Successfully deployed 14 services and updated the global edge configuration.", time: "2 hours ago", group: "Today", meta: "Deployment", - image: "https://i.pravatar.cc/96?img=21", + image: + "https://i.pravatar.cc/96?img=21", progress: null, }, { @@ -28,40 +37,131 @@ const fallbackItems: FeedActivityItem[] = [ actor: "James Wilson", action: "commented on a task", title: "Heatmap spacing", - body: "The insights heatmap needs more padding around the legend on compact displays.", + body: + "The insights heatmap needs more padding around the legend on compact displays.", time: "Yesterday", group: "Yesterday", meta: "Discussion", - image: "https://i.pravatar.cc/96?img=22", + image: + "https://i.pravatar.cc/96?img=22", progress: null, }, ]; +function paginateItems( + items: FeedActivityItem[], + limit: number +) { + return items.slice(0, limit); +} + +function filterFeedItems( + items: FeedActivityItem[], + filter: FeedFilter, + query: string +) { + const normalizedQuery = + query.trim().toLowerCase(); + + return items.filter((item) => { + const matchesFilter = + filter === "All" || + (filter === "Code" && + item.type === "code") || + (filter === "Discussion" && + item.type === "discussion") || + (filter === "Milestones" && + item.type === "milestone"); + + const matchesQuery = + !normalizedQuery || + [ + item.actor, + item.action, + item.title, + item.body, + item.meta, + ].some((value) => + String(value || "") + .toLowerCase() + .includes(normalizedQuery) + ); + + return ( + matchesFilter && + matchesQuery + ); + }); +} + export default function InsightsFeedPage() { - const [items, setItems] = useState(fallbackItems); - const [filter, setFilter] = useState("All"); - const [query, setQuery] = useState(""); - const [debouncedQuery, setDebouncedQuery] = useState(""); - const [visibleCount, setVisibleCount] = useState(6); - const [isLoading, setIsLoading] = useState(false); - const [status, setStatus] = useState(""); + const [items, setItems] = + useState( + fallbackItems + ); + + const [filter, setFilter] = + useState("All"); + + const [query, setQuery] = + useState(""); + + const [ + debouncedQuery, + setDebouncedQuery, + ] = useState(""); + + const [ + visibleCount, + setVisibleCount, + ] = useState(6); + + const [isLoading, setIsLoading] = + useState(false); + + const [status, setStatus] = + useState(""); useEffect(() => { - const controller = new AbortController(); + const controller = + new AbortController(); async function loadFeed() { setIsLoading(true); setStatus(""); + try { - const response = await fetch(`${API_URL}/api/feed`, { - signal: controller.signal, - }); - if (!response.ok) throw new Error("Failed to load feed"); - const body = (await response.json()) as { items: FeedActivityItem[] }; - setItems(body.items.length > 0 ? body.items : fallbackItems); + const response = await fetch( + `${API_URL}/api/feed`, + { + signal: + controller.signal, + } + ); + + if (!response.ok) + throw new Error( + "Failed to load feed" + ); + + const body = + (await response.json()) as { + items: FeedActivityItem[]; + }; + + setItems( + body.items.length > 0 + ? body.items + : fallbackItems + ); } catch (error) { - if ((error as Error).name !== "AbortError") { - setStatus("Showing local activity because the backend feed is unavailable."); + if ( + (error as Error).name !== + "AbortError" + ) { + setStatus( + "Showing local activity because the backend feed is unavailable." + ); } } finally { setIsLoading(false); @@ -69,7 +169,9 @@ export default function InsightsFeedPage() { } void loadFeed(); - return () => controller.abort(); + + return () => + controller.abort(); }, []); useEffect(() => { @@ -77,66 +179,123 @@ export default function InsightsFeedPage() { setDebouncedQuery(query); }, 300); - return () => clearTimeout(timer); + return () => + clearTimeout(timer); }, [query]); - const filteredItems = useMemo(() => { - const normalizedQuery = debouncedQuery.trim().toLowerCase(); - return items.filter((item) => { - const matchesFilter = - filter === "All" || - (filter === "Code" && item.type === "code") || - (filter === "Discussion" && item.type === "discussion") || - (filter === "Milestones" && item.type === "milestone"); - const matchesQuery = - !normalizedQuery || - [item.actor, item.action, item.title, item.body, item.meta].some((value) => - String(value || "").toLowerCase().includes(normalizedQuery) + const filteredItems = + useMemo( + () => + filterFeedItems( + items, + filter, + debouncedQuery + ), + [ + items, + filter, + debouncedQuery, + ] + ); + + const createInsight = + async () => { + const title = + window.prompt( + "Insight title" ); - return matchesFilter && matchesQuery; - }); - }, [filter, items, debouncedQuery]); - - const createInsight = async () => { - const title = window.prompt("Insight title"); - if (!title?.trim()) return; - const body = window.prompt("Insight details"); - if (!body?.trim()) return; - - try { - const session = await supabase?.auth.getSession(); - - const token = session?.data.session?.access_token; - const response = await fetch(`${API_URL}/api/feed`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ title: title.trim(), body: body.trim(), type: "discussion" }), - }); - if (!response.ok) throw new Error("Failed to create insight"); - const result = (await response.json()) as { item: FeedActivityItem }; - setItems((current) => [result.item, ...current]); - setStatus("Insight added to the backend feed."); - } catch { - const localItem: FeedActivityItem = { - id: `local-${Date.now()}`, - type: "discussion", - actor: "You", - action: "created a local insight", - title: title.trim(), - body: body.trim(), - time: "Just now", - group: "Today", - meta: "Local draft", - image: null, - progress: null, - }; - setItems((current) => [localItem, ...current]); - setStatus("Backend was unavailable, so this insight was added locally."); - } - }; + + if (!title?.trim()) return; + + const body = + window.prompt( + "Insight details" + ); + + if (!body?.trim()) return; + + try { + const session = + await supabase?.auth.getSession(); + + const token = + session?.data.session + ?.access_token; + + const response = + await fetch( + `${API_URL}/api/feed`, + { + method: "POST", + headers: { + "Content-Type": + "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + title: + title.trim(), + body: + body.trim(), + type: + "discussion", + }), + } + ); + + if (!response.ok) + throw new Error( + "Failed to create insight" + ); + + const result = + (await response.json()) as { + item: FeedActivityItem; + }; + + setItems( + (current) => [ + result.item, + ...current, + ] + ); + + setStatus( + "Insight added to the backend feed." + ); + } catch { + const localItem: FeedActivityItem = + { + id: `local-${Date.now()}`, + type: + "discussion", + actor: "You", + action: + "created a local insight", + title: + title.trim(), + body: + body.trim(), + time: "Just now", + group: "Today", + meta: + "Local draft", + image: null, + progress: null, + }; + + setItems( + (current) => [ + localItem, + ...current, + ] + ); + + setStatus( + "Backend was unavailable, so this insight was added locally." + ); + } + }; return ( <> @@ -145,21 +304,38 @@ export default function InsightsFeedPage() { query={query} isLoading={isLoading} status={status} - onCreateInsight={createInsight} - onFilterChange={setFilter} - onQueryChange={setQuery} + onCreateInsight={ + createInsight + } + onFilterChange={ + setFilter + } + onQueryChange={ + setQuery + } />
setVisibleCount((count) => count + 4)} + items={paginateItems( + filteredItems, + visibleCount + )} + totalCount={ + filteredItems.length + } + onLoadMore={() => + setVisibleCount( + (count) => + count + 4 + ) + } />
+ ); -} +} \ No newline at end of file