diff --git a/src/components/DataDisplay/ChatMessage/ChatMessage.tsx b/src/components/DataDisplay/ChatMessage/ChatMessage.tsx index fca70c4e..776c46f6 100644 --- a/src/components/DataDisplay/ChatMessage/ChatMessage.tsx +++ b/src/components/DataDisplay/ChatMessage/ChatMessage.tsx @@ -56,4 +56,4 @@ export const ChatMessage = ({ user, msg, sender }: ChatMessageProps) => { ); -}; +}; \ No newline at end of file diff --git a/src/components/DataDisplay/ItemName/ItemName.tsx b/src/components/DataDisplay/ItemName/ItemName.tsx index 189734df..44c2a975 100644 --- a/src/components/DataDisplay/ItemName/ItemName.tsx +++ b/src/components/DataDisplay/ItemName/ItemName.tsx @@ -3,8 +3,7 @@ import { ItemWithMeta } from "$types"; import { TextTranslate } from "../../Shared/TextTranslate"; import { useTranslateCommon } from "@hooks/useTranslate.hook"; import { DisplaySettings, GetItemDisplay, GetSubTypeDisplay } from "@utils/helper"; -import { useQuery } from "@tanstack/react-query"; -import api from "@api/index"; +import { useCacheContext } from "@contexts/cache.context"; import { memo } from "react"; import { faAmberStar, faCyanStar } from "@icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -27,15 +26,8 @@ export type ItemNameProps = { export const ItemName = memo(function ItemName({ color, size, hideQuantity, value, displaySettings }: ItemNameProps) { const theme = useMantineTheme(); - // Fetch data from rust side - const { data: tradableItems } = useQuery({ - queryKey: ["cache_items"], - queryFn: () => api.cache.getTradableItems(), - }); - const { data: weapons } = useQuery({ - queryKey: ["cache_riven_weapons"], - queryFn: () => api.cache.getRivenWeapons(), - }); + // Fetch data from cache context + const { tradableItems, weapons } = useCacheContext(); const GetQuantity = (): string | number => { if (!value) return ""; let quantity = 0; diff --git a/src/components/DataDisplay/RivenPreview/RivenPreview.tsx b/src/components/DataDisplay/RivenPreview/RivenPreview.tsx index f7fa78e4..b7dd2588 100644 --- a/src/components/DataDisplay/RivenPreview/RivenPreview.tsx +++ b/src/components/DataDisplay/RivenPreview/RivenPreview.tsx @@ -1,14 +1,15 @@ +import { memo, useEffect, useState } from "react"; import { Box, Collapse, PaperProps, Text } from "@mantine/core"; import classes from "./RivenPreview.module.css"; import { RivenAttribute, WFMarketTypes } from "$types/index"; import { TauriTypes } from "$types"; -import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import api from "@api/index"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons"; import { useHover } from "@mantine/hooks"; import { getPolarityIcon } from "@icons"; +import { useCacheContext } from "@contexts/cache.context"; export type RivenPreviewProps = { riven: WFMarketTypes.Auction | TauriTypes.StockRiven; @@ -21,7 +22,7 @@ interface RivenAttributeWithUnits extends RivenAttribute { symbol: string; } -export function RivenPreview({ paperProps, riven }: RivenPreviewProps) { +export const RivenPreview = memo(function RivenPreview({ paperProps, riven }: RivenPreviewProps) { // State const { hovered, ref } = useHover(); const [weapon, setWeapon] = useState(undefined); @@ -31,11 +32,8 @@ export function RivenPreview({ paperProps, riven }: RivenPreviewProps) { const [mastery, setMastery] = useState(0); const [reRolls, setReRolls] = useState(0); const [rank, setRank] = useState(0); - // Fetch data from rust side - const { data: weapons } = useQuery({ - queryKey: ["cache_riven_weapons"], - queryFn: () => api.cache.getRivenWeapons(), - }); + // Fetch data from cache context + const { weapons } = useCacheContext(); const { data: allAttributes } = useQuery({ queryKey: ["cache_riven_attributes"], queryFn: () => api.cache.getRivenAttributes(), @@ -150,4 +148,4 @@ export function RivenPreview({ paperProps, riven }: RivenPreviewProps) { ); -} +}); diff --git a/src/components/DataDisplay/WFMOrder/WFMOrder.tsx b/src/components/DataDisplay/WFMOrder/WFMOrder.tsx index bc0c00f4..fb6334fc 100644 --- a/src/components/DataDisplay/WFMOrder/WFMOrder.tsx +++ b/src/components/DataDisplay/WFMOrder/WFMOrder.tsx @@ -156,9 +156,8 @@ export function WFMOrder({ show_border, paperProps, order, footer, show_user, di className={classes.userName} truncate style={{ - borderBottomColor: `var(--qf-user-status-${ - (order.user?.status.toString() || "offline") == "in_game" ? "ingame" : order.user?.status - })`, + borderBottomColor: `var(--qf-user-status-${(order.user?.status.toString() || "offline") == "in_game" ? "ingame" : order.user?.status + })`, borderBottom: "rem(3px) solid", }} > diff --git a/src/components/Layouts/LogIn/LogInLayout.tsx b/src/components/Layouts/LogIn/LogInLayout.tsx index 98883a2c..7d8b24ea 100644 --- a/src/components/Layouts/LogIn/LogInLayout.tsx +++ b/src/components/Layouts/LogIn/LogInLayout.tsx @@ -4,13 +4,14 @@ import { Outlet, useNavigate } from "react-router-dom"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBug, faEnvelope, faGlobe, faHome, faInfoCircle, faMessage } from "@fortawesome/free-solid-svg-icons"; import { useTranslateComponent } from "@hooks/useTranslate.hook"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { NavbarLinkProps, NavbarMinimalColored } from "@components/Layouts/Shared/NavbarMinimalColored"; import { Header } from "@components/Layouts/Shared/Header"; import { useAuthContext } from "@contexts/auth.context"; import { open } from "@tauri-apps/plugin-shell"; import { AddMetric } from "@api/index"; import { faWarframeMarket, facTradingAnalytics } from "@icons"; +import { prefetchRoute } from "../routeLoaders"; export function LogInLayout() { // States @@ -24,7 +25,16 @@ export function LogInLayout() { useTranslate(`navbar.${key}`, { ...context }, i18Key); // States const navigate = useNavigate(); - const links = [ + const handleNavigate = useCallback((link: NavbarLinkProps) => { + if (link.web) open(link.link, "_blank"); + else navigate(link.link); + + if (link.id == lastPage || !link.id) return; + setLastPage(link.id || ""); + AddMetric("active_page", link.id); + }, [navigate, lastPage]); + + const links = useMemo(() => [ { align: "top", id: "home", @@ -32,6 +42,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("home"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("home"), }, { align: "top", @@ -40,6 +51,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("live_scraper"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("liveScraper"), }, { align: "top", @@ -48,6 +60,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("warframe_market"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("warframeMarket"), }, { align: "top", @@ -66,6 +79,7 @@ export function LogInLayout() { ), onClick: (e: NavbarLinkProps) => handleNavigate(e), label: useTranslateNavBar("chat"), + onPrefetch: () => prefetchRoute("chat"), }, { align: "top", @@ -74,6 +88,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("trading_analytics"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("tradingAnalytics"), }, { align: "top", @@ -82,6 +97,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("trade_messages"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("tradeMessages"), }, { align: "top", @@ -99,20 +115,14 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("about"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("about"), }, - ]; + ], [user?.unread_messages, handleNavigate]); + // Effects useEffect(() => { if (user?.qf_banned || user?.wfm_banned) navigate("/error/banned"); - }, [user]); - const handleNavigate = (link: NavbarLinkProps) => { - if (link.web) open(link.link, "_blank"); - else navigate(link.link); - - if (link.id == lastPage || !link.id) return; - setLastPage(link.id || ""); - AddMetric("active_page", link.id); - }; + }, [user, navigate]); return ( - - {!ShowErrorPage() && !IsUserBanned() && ( - <> - }> - }> - } /> + + {!ShowErrorPage() && !IsUserBanned() && ( + <> + }> + }> + } /> + - - }> - }> - } /> - - } /> + }> + }> + } /> + + } /> + + } /> + } /> + } /> + } /> + } /> + } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> - } /> + + )} + {ShowErrorPage() && ( + }> + } /> + + )} + {IsUserBanned() && ( + }> + } /> - - )} - {ShowErrorPage() && ( - }> - } /> - - )} - {IsUserBanned() && ( - }> - } /> - - )} - + )} + ); } diff --git a/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx b/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx index 2dfec3aa..f0ed8b39 100644 --- a/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx +++ b/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx @@ -12,13 +12,21 @@ export type NavbarLinkProps = { web?: boolean; hide?: boolean; onClick?(e: NavbarLinkProps): void; + onPrefetch?(): void; } function NavbarLink(props: NavbarLinkProps) { - const { icon: Icon, label, active, onClick } = props; + const { icon: Icon, label, active, onClick, onPrefetch } = props; return ( - { onClick && onClick(props); }} className={classes.link} data-active={active || undefined} data-rainbow-bg={true}> + { onClick && onClick(props); }} + onMouseEnter={() => { onPrefetch && onPrefetch(); }} + onFocus={() => { onPrefetch && onPrefetch(); }} + className={classes.link} + data-active={active || undefined} + data-rainbow-bg={true} + > {Icon} diff --git a/src/components/Layouts/routeLoaders.ts b/src/components/Layouts/routeLoaders.ts new file mode 100644 index 00000000..a4be9472 --- /dev/null +++ b/src/components/Layouts/routeLoaders.ts @@ -0,0 +1,46 @@ +type RouteLoader = () => Promise; + +export const routeLoaders = { + home: () => import("@pages/home"), + login: () => import("@pages/auth/login"), + debug: () => import("@pages/debug"), + error: () => import("@pages/error"), + banned: () => import("@pages/banned"), + liveScraper: () => import("@pages/live_scraper"), + tradingAnalytics: () => import("@pages/trading_analytics"), + warframeMarket: () => import("@pages/warframe_market"), + chat: () => import("@pages/chat"), + tradeMessages: () => import("@pages/trade_messages"), + about: () => import("@pages/about"), +} as const satisfies Record; + +export type RouteLoaderKey = keyof typeof routeLoaders; + +const prefetched = new Set(); + +export const prefetchRoute = (key: RouteLoaderKey) => { + if (prefetched.has(key)) return; + prefetched.add(key); + routeLoaders[key]().catch(() => { + prefetched.delete(key); + }); +}; + +export const prefetchRoutes = (keys: RouteLoaderKey[]) => { + keys.forEach(prefetchRoute); +}; + +export const prefetchLoggedInRoutes = () => { + prefetchRoutes([ + "home", + "liveScraper", + "tradingAnalytics", + "tradeMessages", + "warframeMarket", + "chat", + "about", + "login", + "error", + "banned", + ]); +}; diff --git a/src/contexts/app.context.tsx b/src/contexts/app.context.tsx index 673440d9..f45ab95f 100644 --- a/src/contexts/app.context.tsx +++ b/src/contexts/app.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; import api from "@api/index"; import { QuantframeApiTypes, ResponseError, TauriTypes } from "$types"; import { AuthContextProvider } from "./auth.context"; @@ -14,6 +14,7 @@ import { useTranslateCommon, useTranslateComponent, useTranslateContexts } from import { resolveResource } from "@tauri-apps/api/path"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { LiveScraperContextProvider } from "./liveScraper.context"; +import { CacheContextProvider } from "./cache.context"; import { notifications } from "@mantine/notifications"; import { TextTranslate } from "@components/Shared/TextTranslate"; import { useTauriEvent } from "@hooks/useTauriEvent.hook"; @@ -202,14 +203,26 @@ export function AppContextProvider({ children }: AppContextProviderProps) { console.error("Error playing sound:", error); }); }); - return () => {}; + return () => { }; }, []); + const contextValue = useMemo(() => ({ + settings, + alerts: alerts?.results || [], + app_info, + app_error: error, + checkForUpdates, + loading, + setLang + }), [settings, alerts?.results, app_info, error, checkForUpdates, loading, setLang]); + return ( - + {!loading && ( - {children} + + {children} + )} diff --git a/src/contexts/auth.context.tsx b/src/contexts/auth.context.tsx index cc86e1f6..55a81a34 100644 --- a/src/contexts/auth.context.tsx +++ b/src/contexts/auth.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { OffTauriDataEvent, OnTauriDataEvent } from "@api/index"; import { TauriTypes } from "$types"; import api from "@api/index"; @@ -50,5 +50,7 @@ export function AuthContextProvider({ children }: TauriContextProviderProps) { }; }, []); - return {children}; + const contextValue = useMemo(() => ({ user }), [user]); + + return {children}; } diff --git a/src/contexts/cache.context.tsx b/src/contexts/cache.context.tsx new file mode 100644 index 00000000..3b9be4c1 --- /dev/null +++ b/src/contexts/cache.context.tsx @@ -0,0 +1,44 @@ +import { createContext, useContext, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import api from "@api/index"; +import { TauriTypes } from "$types"; + +export type CacheContextProps = { + tradableItems: TauriTypes.CacheTradableItem[]; + weapons: TauriTypes.CacheRivenWeapon[]; + isLoading: boolean; +}; + +export type CacheContextProviderProps = { + children: React.ReactNode; +}; + +export const CacheContext = createContext({ + tradableItems: [], + weapons: [], + isLoading: true, +}); + +export const useCacheContext = () => useContext(CacheContext); + +export function CacheContextProvider({ children }: CacheContextProviderProps) { + const { data: tradableItems, isLoading: isLoadingItems } = useQuery({ + queryKey: ["cache_items"], + queryFn: () => api.cache.getTradableItems(), + staleTime: Infinity, // Cache forever - items rarely change + }); + + const { data: weapons, isLoading: isLoadingWeapons } = useQuery({ + queryKey: ["cache_riven_weapons"], + queryFn: () => api.cache.getRivenWeapons(), + staleTime: Infinity, // same here + }); + + const contextValue = useMemo(() => ({ + tradableItems: tradableItems || [], + weapons: weapons || [], + isLoading: isLoadingItems || isLoadingWeapons, + }), [tradableItems, weapons, isLoadingItems, isLoadingWeapons]); + + return {children}; +} diff --git a/src/contexts/liveScraper.context.tsx b/src/contexts/liveScraper.context.tsx index 1af337c4..aebe08d3 100644 --- a/src/contexts/liveScraper.context.tsx +++ b/src/contexts/liveScraper.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { TauriTypes } from "$types"; import api from "../api"; import { invoke } from "@tauri-apps/api/core"; @@ -53,5 +53,7 @@ export function LiveScraperContextProvider({ children }: LiveScraperContextProvi .catch((e) => console.error("Error checking initialization:", e)); }, []); - return {children}; + const contextValue = useMemo(() => ({ is_running, message }), [is_running, message]); + + return {children}; } diff --git a/src/pages/live_scraper/Tabs/Item/index.tsx b/src/pages/live_scraper/Tabs/Item/index.tsx index b39cd5c9..3fb4c8f6 100644 --- a/src/pages/live_scraper/Tabs/Item/index.tsx +++ b/src/pages/live_scraper/Tabs/Item/index.tsx @@ -198,8 +198,8 @@ export const ItemPanel = ({ isActive }: ItemPanelProps = {}) => { }} mt={"md"} striped - fetching={paginationQuery.isLoading} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/live_scraper/Tabs/Riven/index.tsx b/src/pages/live_scraper/Tabs/Riven/index.tsx index 7a44364d..2a90476e 100644 --- a/src/pages/live_scraper/Tabs/Riven/index.tsx +++ b/src/pages/live_scraper/Tabs/Riven/index.tsx @@ -194,8 +194,8 @@ export const RivenPanel = ({ isActive }: RivenPanelProps = {}) => { }} mt={"md"} striped - fetching={paginationQuery.isLoading} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/live_scraper/Tabs/WishList/index.tsx b/src/pages/live_scraper/Tabs/WishList/index.tsx index 655d4285..5fae07c9 100644 --- a/src/pages/live_scraper/Tabs/WishList/index.tsx +++ b/src/pages/live_scraper/Tabs/WishList/index.tsx @@ -173,8 +173,8 @@ export const WishListPanel = ({ isActive }: WishListPanelProps = {}) => { }} mt={"md"} striped - fetching={paginationQuery.isLoading} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/live_scraper/index.tsx b/src/pages/live_scraper/index.tsx index 404371d3..711bebe4 100644 --- a/src/pages/live_scraper/index.tsx +++ b/src/pages/live_scraper/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Box, Container, Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { ItemPanel, RivenPanel, WishListPanel } from "./Tabs"; @@ -13,7 +14,7 @@ export default function LiveScraperPage() { const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("item.title"), component: (isActive: boolean) => , @@ -29,7 +30,7 @@ export default function LiveScraperPage() { component: (isActive: boolean) => , id: "wish_list", }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "live_scraper.active_tab", @@ -51,7 +52,7 @@ export default function LiveScraperPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} diff --git a/src/pages/trade_messages/helpers/TradeEntryList.tsx b/src/pages/trade_messages/helpers/TradeEntryList.tsx index 7ed96e93..034f7463 100644 --- a/src/pages/trade_messages/helpers/TradeEntryList.tsx +++ b/src/pages/trade_messages/helpers/TradeEntryList.tsx @@ -237,7 +237,7 @@ export const TradeEntryList = ({ striped customLoader={} fetching={IsLoading()} - records={paginationQuery.data?.results || []} + records={IsLoading() ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.values.page, paginationQuery.data?.total_pages)} onPageChange={(page) => queryData.setFieldValue("page", page)} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trade_messages/index.tsx b/src/pages/trade_messages/index.tsx index dd7f9a70..92957789 100644 --- a/src/pages/trade_messages/index.tsx +++ b/src/pages/trade_messages/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { ItemPanel, RivenPanel, CustomPanel } from "./Tabs"; @@ -12,7 +13,7 @@ export default function TradeMessagesPage() { const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("item.title"), component: (isActive: boolean) => , @@ -29,7 +30,7 @@ export default function TradeMessagesPage() { id: "riven", isPremium: true, }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "trade_messages_active_tab", @@ -47,7 +48,7 @@ export default function TradeMessagesPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} diff --git a/src/pages/trading_analytics/Tabs/Item/index.tsx b/src/pages/trading_analytics/Tabs/Item/index.tsx index e7f809f1..5834c867 100644 --- a/src/pages/trading_analytics/Tabs/Item/index.tsx +++ b/src/pages/trading_analytics/Tabs/Item/index.tsx @@ -166,7 +166,7 @@ export const ItemPanel = ({ isActive }: ItemPanelProps = {}) => { striped customLoader={} fetching={IsLoading()} - records={paginationQuery.data?.results || []} + records={IsLoading() ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.values.page, paginationQuery.data?.total_pages)} onPageChange={(page) => queryData.setFieldValue("page", page)} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trading_analytics/Tabs/Riven/index.tsx b/src/pages/trading_analytics/Tabs/Riven/index.tsx index dc660f23..a8d38e2d 100644 --- a/src/pages/trading_analytics/Tabs/Riven/index.tsx +++ b/src/pages/trading_analytics/Tabs/Riven/index.tsx @@ -154,7 +154,7 @@ export const RivenPanel = ({ isActive }: RivenPanelProps = {}) => { mt={"md"} striped fetching={IsLoading()} - records={paginationQuery.data?.results || []} + records={IsLoading() ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.values.page, paginationQuery.data?.total_pages)} onPageChange={(page) => queryData.setFieldValue("page", page)} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trading_analytics/Tabs/Transaction/index.tsx b/src/pages/trading_analytics/Tabs/Transaction/index.tsx index 4e885804..b1b1e75b 100644 --- a/src/pages/trading_analytics/Tabs/Transaction/index.tsx +++ b/src/pages/trading_analytics/Tabs/Transaction/index.tsx @@ -1,6 +1,6 @@ import { Box, Grid, Group, NumberFormatter, Paper, Table, Text, Title } from "@mantine/core"; import { useLocalStorage } from "@mantine/hooks"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useTransition } from "react"; import { TauriTypes } from "$types"; import { useQueries } from "./queries"; import { useTauriEvent } from "@hooks/useTauriEvent.hook"; @@ -58,13 +58,17 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { const [filterOpened, setFilterOpened] = useState(false); const [canExport, setCanExport] = useState(false); + // Progressive rendering === allows navigation during table render (without it ui freezes until table loads) + const [isPending, startTransition] = useTransition(); + const [displayedRecords, setDisplayedRecords] = useState([]); + // Check permissions for export on mount useEffect(() => { HasPermission(TauriTypes.PermissionsFlags.EXPORT_DATA).then((res) => setCanExport(res)); }, []); // Queries - const { paginationQuery, financialReportQuery, refetchQueries } = useQueries({ queryData, isActive }); + const { paginationQuery, financialReportQuery, refetchQueries } = useQueries({ queryData, isActive, loadFinancialReport: showReport }); const handleRefresh = () => { console.log("Refreshing transactions due to Tauri event"); refetchQueries(); @@ -94,6 +98,17 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { // Use the custom hook for Tauri events useTauriEvent(TauriTypes.Events.RefreshTransactions, handleRefresh, []); + // Progressive rendering + useEffect(() => { + if (paginationQuery.isFetching) { + setDisplayedRecords([]); + } else if (paginationQuery.data?.results) { + startTransition(() => { + setDisplayedRecords(paginationQuery.data?.results || []); + }); + } + }, [paginationQuery.isFetching, paginationQuery.data?.results]); + return ( {" "} @@ -200,8 +215,8 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { className={`${classes.databaseTransactions} ${useHasAlert() ? classes.alert : ""} ${filterOpened ? classes.filterOpened : ""}`} mt={"md"} striped - fetching={paginationQuery.isLoading || calculateTaxMutation.isPending} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching || isPending || calculateTaxMutation.isPending} + records={displayedRecords} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} @@ -367,7 +382,7 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { className={`${classes.databaseTradingPartners} ${useHasAlert() ? classes.alert : ""} ${filterOpened ? classes.filterOpened : ""}`} mt={"md"} striped - fetching={paginationQuery.isLoading || calculateTaxMutation.isPending} + fetching={paginationQuery.isFetching || calculateTaxMutation.isPending} records={financialReportQuery.data?.properties.trading_partners || []} idAccessor={"properties.user"} // define columns diff --git a/src/pages/trading_analytics/Tabs/Transaction/queries.ts b/src/pages/trading_analytics/Tabs/Transaction/queries.ts index 291f4248..abe0313b 100644 --- a/src/pages/trading_analytics/Tabs/Transaction/queries.ts +++ b/src/pages/trading_analytics/Tabs/Transaction/queries.ts @@ -5,9 +5,10 @@ import api from "@api/index"; interface QueriesHooks { queryData: TauriTypes.TransactionControllerGetListParams; isActive?: boolean; + loadFinancialReport?: boolean; } -export const useQueries = ({ queryData, isActive }: QueriesHooks) => { +export const useQueries = ({ queryData, isActive, loadFinancialReport = false }: QueriesHooks) => { const getPaginationQuery = useQuery({ queryKey: ["get_transaction_pagination", queryData], queryFn: () => api.transaction.getPagination(queryData), @@ -18,7 +19,7 @@ export const useQueries = ({ queryData, isActive }: QueriesHooks) => { queryKey: ["get_transaction_financial_report", queryData], queryFn: () => api.transaction.getFinancialReport({ ...queryData, page: 1, limit: -1 }), retry: false, - enabled: isActive, + enabled: isActive && loadFinancialReport, }); const refetchQueries = () => { getPaginationQuery.refetch(); diff --git a/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx b/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx index 6a2b7f8d..a522a45f 100644 --- a/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx +++ b/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx @@ -91,7 +91,7 @@ export const WarframeGDPRParser = ({ isActive }: WarframeGDPRParserProps = {}) = {tabs.map((tab) => ( - {tab.component(isActive !== false)} + {activeTab === tab.id && tab.component(isActive !== false)} ))} diff --git a/src/pages/trading_analytics/index.tsx b/src/pages/trading_analytics/index.tsx index dee1ce66..76e21388 100644 --- a/src/pages/trading_analytics/index.tsx +++ b/src/pages/trading_analytics/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { TransactionPanel, ItemPanel, RivenPanel, UserPanel, WarframeGDPRParser } from "./Tabs"; @@ -12,7 +13,7 @@ export default function TradingAnalyticsPage() { const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("transaction.title"), component: (isActive: boolean) => , @@ -41,7 +42,7 @@ export default function TradingAnalyticsPage() { id: "wfgdpr", isPremium: false, }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "trading_analytics_active_tab", @@ -59,7 +60,7 @@ export default function TradingAnalyticsPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} diff --git a/src/pages/warframe_market/index.tsx b/src/pages/warframe_market/index.tsx index 0f80f576..168ef29d 100644 --- a/src/pages/warframe_market/index.tsx +++ b/src/pages/warframe_market/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Container, Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { OrderPanel } from "./Tabs/Orders"; @@ -12,14 +13,14 @@ export default function WarframeMarketPage() { useTranslatePages(`warframe_market.${key}`, { ...context }, i18Key); const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslate(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("orders.title"), component: (isActive: boolean) => , id: "orders", }, { label: useTranslateTabs("auctions.title"), component: (isActive: boolean) => , id: "auctions" }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "warframe_market.active_tab", defaultValue: tabs[0].id, @@ -36,7 +37,7 @@ export default function WarframeMarketPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))}