import { Box } from "../../../../ui"; import { useEffect, useMemo } from "react"; import type { PaneProps } from "../../../../types/plugin"; import type { MarketNewsItem } from "../../../../types/news-source"; import { useLoadNewsStory, useNewsArticles, useNewsTableLoadMore } from "../../../../news/hooks"; import { useDebouncedPluginPaneState, usePluginPaneState } from "../../../runtime"; import { Tabs, usePaneHeaderTabs } from "../../../../components"; import { NewsDetailView, useNewsArticleDetail } from "./news/detail-view"; import { NewsArticleStackView, newsTableStatusContent, type NewsSortPreference, } from "./news/table"; import { useNewsArticleFooter } from "./news/footer"; import { useNewsReadState } from "./read-state"; import { usePersistedNewsArticles } from "./persisted-articles"; import { NEWS_QUERY_PRESETS, SECTOR_NEWS_SECTORS, type SectorNewsSelection, sectorNewsLabel, } from "./news/query-presets"; const SECTOR_TABS = ["all", ...SECTOR_NEWS_SECTORS] as const; const DEFAULT_SORT: NewsSortPreference = { columnId: "time", direction: "desc" }; /** * Every sector tab filters the one all-sector response instead of issuing a * second request for a slice of the same stories. */ function useIndustryArticles(sector: SectorNewsSelection): { articles: MarketNewsItem[]; allArticles: MarketNewsItem[]; loading: boolean; error: string | null; newsState: ReturnType; } { const allState = useNewsArticles(NEWS_QUERY_PRESETS.sectorAll); const allArticles = usePersistedNewsArticles("industry:sector:all:articles", allState.articles); const articles = useMemo(() => ( sector === "all" ? allArticles : allArticles.filter((article) => ( article.sectors.some((entry) => entry.toLowerCase() === sector) )) ), [allArticles, sector]); return { articles, allArticles, loading: allState.phase === "loading" || (allState.phase === "refreshing" && allArticles.length === 0), error: allState.error, newsState: allState, }; } export function IndustryPane({ focused, width, height }: PaneProps) { const [category, setCategory] = usePluginPaneState("industry:category", "all"); const [selectedArticleId, setSelectedArticleId] = useDebouncedPluginPaneState("industry:selectedArticleId", null); const [sortPreference, setSortPreference] = usePluginPaneState("industry:sort", DEFAULT_SORT); const { articles, allArticles, loading, error, newsState } = useIndustryArticles(category); const { scrollRef, onBodyScrollActivity } = useNewsTableLoadMore(NEWS_QUERY_PRESETS.sectorAll, newsState); const loadNewsStory = useLoadNewsStory(); const { detailArticle, detailLoading, detailError, openArticle, closeDetail } = useNewsArticleDetail( articles, loadNewsStory, "industry:openArticleId", ); const { readArticleIds, markArticleRead } = useNewsReadState(); const tabs = useMemo(() => SECTOR_TABS.map((cat) => ({ value: cat, label: sectorNewsLabel(cat), })), []); useEffect(() => { setSelectedArticleId(null); }, [category, setSelectedArticleId]); useNewsArticleFooter({ registrationId: "news-wire:industry", focused, article: detailArticle, loading: detailLoading || (loading && allArticles.length > 0), error: [error, detailError].filter(Boolean).join(" ") || null, }); const selectCategory = (value: string) => setCategory(value as SectorNewsSelection); // An open story owns h/l and the arrows; the sector strip must not switch under it. const tabsFocused = focused && !detailArticle; const tabsInHeader = usePaneHeaderTabs({ tabs, activeValue: category, onSelect: selectCategory, focused: tabsFocused }); const rootBefore = tabsInHeader ? undefined : ( ); const detailContent = detailArticle ? ( ) : ( ); return ( ); }