diff --git a/client/src/api.ts b/client/src/api.ts index 728ef6e00..25098c95e 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -47,7 +47,7 @@ import { EditAcademyOnlineAccessFormValues, CentralIdentityUserLicenseResult, CentralIdentityAppLicense, - StoreDigitalDeliveryOption, StoreOrderWithStripeSession, + StoreDigitalDeliveryOption, StoreOrderWithStripeSession, StoreOrderListItem, OrderCharge, OrderSession, CentralIdentityOrgAdminResult, @@ -762,7 +762,7 @@ class API { query?: string; }) { const res = await axios.get< - ConductorInfiniteScrollResponse + ConductorInfiniteScrollResponse >("/store/admin/orders", { params, }); diff --git a/client/src/screens/conductor/controlpanel/StoreManager/index.tsx b/client/src/screens/conductor/controlpanel/StoreManager/index.tsx index 9288222c3..7aa1a1732 100644 --- a/client/src/screens/conductor/controlpanel/StoreManager/index.tsx +++ b/client/src/screens/conductor/controlpanel/StoreManager/index.tsx @@ -1,20 +1,22 @@ import { Link } from "react-router-dom"; -import { Badge, Breadcrumb, Button, Heading, Select, Stack } from "@libretexts/davis-react"; +import { Badge, Breadcrumb, Button, Heading, Input, Select, Stack } from "@libretexts/davis-react"; import type { BadgeVariant } from "@libretexts/davis-react"; -import { StoreOrderWithStripeSession } from "../../../../types"; +import { StoreOrderListItem } from "../../../../types"; import useGlobalError from "../../../../components/error/ErrorHooks"; import SupportCenterTable from "../../../../components/support/SupportCenterTable"; import { useInfiniteQuery } from "@tanstack/react-query"; import api from "../../../../api"; import useDocumentTitle from "../../../../hooks/useDocumentTitle"; +import useDebounce from "../../../../hooks/useDebounce"; import { IconCloudComputing, IconDownload, IconEye, + IconSearch, } from "@tabler/icons-react"; import { useNotifications } from "../../../../context/NotificationContext"; import { formatPrice, truncateOrderId } from "../../../../utils/storeHelpers"; -import { useState } from "react"; +import { useMemo, useState } from "react"; function luluStatusVariant(status?: string | null): BadgeVariant { if (!status) return "default"; @@ -36,12 +38,21 @@ const StoreManager = () => { const limit = 25; const { addNotification } = useNotifications(); const { handleGlobalError } = useGlobalError(); + const { debounce } = useDebounce(); const [statusFilter, setStatusFilter] = useState("all"); const [luluStatusFilter, setLuluStatusFilter] = useState("all"); + const [searchInput, setSearchInput] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + + // Debounce the value that actually drives the query so each keystroke doesn't refetch. + const debouncedSetQuery = useMemo( + () => debounce((value: string) => setSearchQuery(value.trim()), 400), + [] + ); const { data, isFetching, isInitialLoading, fetchNextPage } = useInfiniteQuery({ - queryKey: ["store-orders", limit, statusFilter, luluStatusFilter], + queryKey: ["store-orders", limit, statusFilter, luluStatusFilter, searchQuery], queryFn: async ({ pageParam = null }) => { const response = await api.adminGetStoreOrders({ limit, @@ -49,6 +60,7 @@ const StoreManager = () => { status: statusFilter === "all" ? undefined : statusFilter, lulu_status: luluStatusFilter === "all" ? undefined : luluStatusFilter, + query: searchQuery || undefined, }); if (response.data.err) { @@ -82,7 +94,21 @@ const StoreManager = () => {
-
+
+
+ } + onChange={(e) => { + setSearchInput(e.target.value); + debouncedSetQuery(e.target.value); + }} + /> +