diff --git a/webui/src/components/table/DataGrid.tsx b/webui/src/components/table/DataGrid.tsx index c28c0c4..b6f6419 100644 --- a/webui/src/components/table/DataGrid.tsx +++ b/webui/src/components/table/DataGrid.tsx @@ -126,11 +126,16 @@ type DataGridBaseProps = { /** * Query ownership is deliberately explicit: client grids receive all rows and - * DataGrid queries them locally; server grids receive one page and must handle - * the complete query through `onQueryChange`. + * DataGrid queries them locally, while optional `onQueryChange` observers can + * explain the current match. Server grids receive one page and must handle the + * complete query through `onQueryChange`. */ export type DataGridProps = DataGridBaseProps & ( - | {pagination?: DataGridClientPagination;onQueryChange?: never;} + | { + pagination?: DataGridClientPagination; + /** Observe client-side filters and sorting without taking ownership of them. */ + onQueryChange?: (query: DataGridQueryState) => void; + } | {pagination: DataGridServerPagination;onQueryChange: (query: DataGridQueryState) => void;} ); @@ -256,7 +261,7 @@ export default function DataGrid({ const serverQueryMode = pagination?.mode === "server"; const onQueryChangeRef = useRef(onQueryChange); const serverPaginationRef = useRef(serverQueryMode ? pagination : null); - const lastServerQueryRef = useRef(null); + const lastQueryRef = useRef(null); const lastLayoutSignatureRef = useRef(null); const lastContainerWidthRef = useRef(undefined); const [measuredWidths, setMeasuredWidths] = useState>({}); @@ -265,7 +270,6 @@ export default function DataGrid({ useEffect(() => { serverPaginationRef.current = serverQueryMode ? pagination as DataGridServerPagination : null; - if (!serverQueryMode) lastServerQueryRef.current = null; }, [pagination, serverQueryMode]); useEffect(() => { @@ -510,18 +514,23 @@ export default function DataGrid({ }, [openFilterColumnId]); useEffect(() => { - if (!serverQueryMode) return; - const query = { + const nextQuery = { sort: state.sort ?? null, filters: { ...(state.filters ?? {}) } }; - const previousQuery = lastServerQueryRef.current; - lastServerQueryRef.current = query; + const previousQuery = lastQueryRef.current; + lastQueryRef.current = nextQuery; const serverPagination = serverPaginationRef.current; - if (previousQuery && !dataGridQueriesEqual(previousQuery, query) && serverPagination && serverPagination.page !== 1) { + if ( + serverQueryMode && + previousQuery && + !dataGridQueriesEqual(previousQuery, nextQuery) && + serverPagination && + serverPagination.page !== 1 + ) { serverPagination.onPageChange(1); } - onQueryChangeRef.current?.(query); + onQueryChangeRef.current?.(nextQuery); }, [serverQueryMode, state.sort, state.filters]); const filterTypes = useMemo(() => { diff --git a/webui/src/types.ts b/webui/src/types.ts index 9ceb2d5..50f0cca 100644 --- a/webui/src/types.ts +++ b/webui/src/types.ts @@ -857,7 +857,7 @@ export type FilesFileFolder = { }; export type FilesFileSpacesResponse = { spaces: FilesFileSpace[] }; -export type FilesFileListResponse = { files: FilesManagedFile[]; cursor?: string | null; next_cursor?: string | null }; +export type FilesFileListResponse = { files: FilesManagedFile[]; total: number; cursor?: string | null; next_cursor?: string | null }; export type FilesFileFoldersResponse = { folders: FilesFileFolder[]; cursor?: string | null; next_cursor?: string | null }; export type FilesFileDeltaResponse = { files: FilesManagedFile[]; @@ -917,7 +917,7 @@ export type FilesFileExplorerUiCapability = { FolderTree: ComponentType; ManagedFileChooser?: ComponentType; listFileSpaces?: (settings: ApiSettings) => Promise; - listFiles?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; page_size?: number; cursor?: string | null }) => Promise; + listFiles?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; campaign_usage?: "linked" | "unlinked"; audit_relevant?: boolean; page_size?: number; cursor?: string | null }) => Promise; listFilesDelta?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; since?: string | null; limit?: number }) => Promise; listFolders?: (settings: ApiSettings, params: { owner_type: FilesOwnerType; owner_id: string; page_size?: number; cursor?: string | null }) => Promise; resolveFilePatterns?: (