diff --git a/src/pages/usage/events-tab/index.tsx b/src/pages/usage/events-tab/index.tsx index 3f90a431..4943427a 100644 --- a/src/pages/usage/events-tab/index.tsx +++ b/src/pages/usage/events-tab/index.tsx @@ -9,23 +9,15 @@ * event-type selects ride in the bar's ``extra`` slot. */ import { useAccess, useIntl } from '@umijs/max'; +import { useMemoizedFn } from 'ahooks'; import { Input, Select, Table, Tag } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState -} from 'react'; -import { - queryResourceEvents, - ResourceEventItem, - ResourceEventsResponse -} from '../apis/resource'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { ResourceEventItem } from '../apis/resource'; import ResourceFilterBar from '../components/resource-filter-bar'; import { parseRollup } from '../utils/time-buckets'; +import useQueryResourceEvents from './services/use-query-resource-events'; // Only these four are ever emitted (see resource_event_logger): create/delete // + the metering-window pair. updated/attached/detached exist as enum values @@ -115,15 +107,17 @@ const ResourceEvents: React.FC = () => { eventTypes: string[]; nameQuery: string; page: number; + perPage: number; }; const [queryParams, setQueryParams] = useState({ dateRange: [dayjs().subtract(29, 'day'), dayjs()], resourceType: undefined, eventTypes: [], nameQuery: '', + perPage: 50, page: 1 }); - const [data, setData] = useState(null); + const { detailData: data, loading, fetchData } = useQueryResourceEvents(); // Latest params, so the stable debounced name handler reads current values. const queryRef = useRef(queryParams); @@ -132,30 +126,20 @@ const ResourceEvents: React.FC = () => { // Single fetch entry point: merge the patch into the current params, persist // them, then request — triggered from each handler rather than from effect // dependencies, so there's exactly one request per user action. - const fetchEvents = useCallback( - async (patch: Partial) => { - const params = { ...queryRef.current, ...patch }; - setQueryParams(params); - try { - const res = await queryResourceEvents({ - start_date: params.dateRange[0].format('YYYY-MM-DD'), - end_date: params.dateRange[1].format('YYYY-MM-DD'), - scope, - resource_types: params.resourceType - ? [params.resourceType] - : undefined, - resource_name: params.nameQuery || undefined, - event_types: params.eventTypes, - page: params.page, - perPage: 50 - }); - setData(res); - } catch { - // Keep last response on failure. - } - }, - [scope] - ); + const fetchEvents = useMemoizedFn((patch: Partial) => { + const params = { ...queryRef.current, ...patch }; + setQueryParams(params); + return fetchData({ + start_date: params.dateRange[0].format('YYYY-MM-DD'), + end_date: params.dateRange[1].format('YYYY-MM-DD'), + scope, + resource_types: params.resourceType ? [params.resourceType] : undefined, + resource_name: params.nameQuery || undefined, + event_types: params.eventTypes, + page: params.page, + perPage: 50 + }); + }); // First load only — subsequent fetches are driven by the handlers below. useEffect(() => { @@ -278,11 +262,17 @@ const ResourceEvents: React.FC = () => { dataSource={data?.items ?? []} columns={columns as any} style={{ marginTop: 24 }} + loading={{ + spinning: loading, + size: 'middle' + }} pagination={{ size: 'middle', current: queryParams.page, - pageSize: data?.pagination.perPage ?? 50, - total: data?.pagination.total ?? 0, + pageSize: data?.pagination?.perPage ?? 50, + total: data?.pagination?.total ?? 0, + showSizeChanger: false, + hideOnSinglePage: queryParams.perPage === 50, onChange: (p) => fetchEvents({ page: p }) }} /> diff --git a/src/pages/usage/events-tab/services/use-query-resource-events.ts b/src/pages/usage/events-tab/services/use-query-resource-events.ts new file mode 100644 index 00000000..407ecd59 --- /dev/null +++ b/src/pages/usage/events-tab/services/use-query-resource-events.ts @@ -0,0 +1,30 @@ +import { useQueryData } from '@/hooks/use-query-data-list'; +import { + queryResourceEvents, + ResourceEventsResponse +} from '../../apis/resource'; + +type ResourceEventsParams = Parameters[0]; + +/** + * Wraps the `queryResourceEvents` request with shared loading state and + * in-flight cancellation (the previous request is cancelled on each new fetch + * and on unmount), so rapid filter/name-search/page changes can't race a stale + * response onto the table. + */ +export default function useQueryResourceEvents(option?: { key?: string }) { + const { detailData, loading, cancelRequest, fetchData } = useQueryData< + ResourceEventsResponse, + ResourceEventsParams + >({ + fetchDetail: queryResourceEvents, + key: option?.key || 'resourceEvents' + }); + + return { + detailData, + loading, + cancelRequest, + fetchData + }; +}