From 4e5584c9f7aff70237253a44a30653a362d9d2f0 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Tue, 12 Aug 2025 15:01:33 -0700 Subject: [PATCH 01/16] store page and page size state in globalstate and persist --- vite-app/src/GlobalState.tsx | 86 +++++++++++++++++++++ vite-app/src/components/EvaluationTable.tsx | 41 ++++------ 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/vite-app/src/GlobalState.tsx b/vite-app/src/GlobalState.tsx index 14db70ae..661b0fe6 100644 --- a/vite-app/src/GlobalState.tsx +++ b/vite-app/src/GlobalState.tsx @@ -12,6 +12,12 @@ const DEFAULT_PIVOT_CONFIG: PivotConfig = { filters: [], }; +// Default pagination configuration +const DEFAULT_PAGINATION_CONFIG = { + currentPage: 1, + pageSize: 25, +}; + export class GlobalState { isConnected: boolean = false; // rollout_id -> EvaluationRow @@ -20,10 +26,17 @@ export class GlobalState { expandedRows: Record = {}; // Pivot configuration pivotConfig: PivotConfig; + // Pagination configuration + currentPage: number; + pageSize: number; constructor() { // Load pivot config from localStorage or use defaults this.pivotConfig = this.loadPivotConfig(); + // Load pagination config from localStorage or use defaults + const paginationConfig = this.loadPaginationConfig(); + this.currentPage = paginationConfig.currentPage; + this.pageSize = paginationConfig.pageSize; makeAutoObservable(this); } @@ -42,6 +55,21 @@ export class GlobalState { return { ...DEFAULT_PIVOT_CONFIG }; } + // Load pagination configuration from localStorage + private loadPaginationConfig() { + try { + const stored = localStorage.getItem("paginationConfig"); + if (stored) { + const parsed = JSON.parse(stored); + // Merge with defaults to handle any missing properties + return { ...DEFAULT_PAGINATION_CONFIG, ...parsed }; + } + } catch (error) { + console.warn("Failed to load pagination config from localStorage:", error); + } + return { ...DEFAULT_PAGINATION_CONFIG }; + } + // Save pivot configuration to localStorage private savePivotConfig() { try { @@ -51,12 +79,35 @@ export class GlobalState { } } + // Save pagination configuration to localStorage + private savePaginationConfig() { + try { + localStorage.setItem("paginationConfig", JSON.stringify({ + currentPage: this.currentPage, + pageSize: this.pageSize, + })); + } catch (error) { + console.warn("Failed to save pagination config to localStorage:", error); + } + } + // Update pivot configuration and save to localStorage updatePivotConfig(updates: Partial) { Object.assign(this.pivotConfig, updates); this.savePivotConfig(); } + // Update pagination configuration and save to localStorage + updatePaginationConfig(updates: Partial<{ currentPage: number; pageSize: number }>) { + if (updates.currentPage !== undefined) { + this.currentPage = updates.currentPage; + } + if (updates.pageSize !== undefined) { + this.pageSize = updates.pageSize; + } + this.savePaginationConfig(); + } + // Reset pivot configuration to defaults resetPivotConfig() { this.pivotConfig = { @@ -66,6 +117,26 @@ export class GlobalState { this.savePivotConfig(); } + // Reset pagination configuration to defaults + resetPaginationConfig() { + this.currentPage = DEFAULT_PAGINATION_CONFIG.currentPage; + this.pageSize = DEFAULT_PAGINATION_CONFIG.pageSize; + this.savePaginationConfig(); + } + + // Set current page + setCurrentPage(page: number) { + this.currentPage = page; + this.savePaginationConfig(); + } + + // Set page size + setPageSize(size: number) { + this.pageSize = size; + this.currentPage = 1; // Reset to first page when changing page size + this.savePaginationConfig(); + } + upsertRows(dataset: EvaluationRow[]) { dataset.forEach((row) => { if (!row.execution_metadata?.rollout_id) { @@ -73,6 +144,9 @@ export class GlobalState { } this.dataset[row.execution_metadata.rollout_id] = row; }); + // Reset to first page when dataset changes + this.currentPage = 1; + this.savePaginationConfig(); } toggleRowExpansion(rolloutId?: string) { @@ -122,4 +196,16 @@ export class GlobalState { get totalCount() { return Object.keys(this.dataset).length; } + + get totalPages() { + return Math.ceil(this.totalCount / this.pageSize); + } + + get startRow() { + return (this.currentPage - 1) * this.pageSize + 1; + } + + get endRow() { + return Math.min(this.currentPage * this.pageSize, this.totalCount); + } } diff --git a/vite-app/src/components/EvaluationTable.tsx b/vite-app/src/components/EvaluationTable.tsx index fb470b9e..d60260a5 100644 --- a/vite-app/src/components/EvaluationTable.tsx +++ b/vite-app/src/components/EvaluationTable.tsx @@ -1,5 +1,4 @@ import { observer } from "mobx-react"; -import { useState, useEffect } from "react"; import { state } from "../App"; import { EvaluationRow } from "./EvaluationRow"; import Button from "./Button"; @@ -32,28 +31,19 @@ const TableBody = observer( // Dedicated component for rendering the list - following MobX best practices export const EvaluationTable = observer(() => { - const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(25); - const totalRows = state.sortedDataset.length; - const totalPages = Math.ceil(totalRows / pageSize); - const startRow = (currentPage - 1) * pageSize + 1; - const endRow = Math.min(currentPage * pageSize, totalRows); + const totalPages = state.totalPages; + const startRow = state.startRow; + const endRow = state.endRow; const handlePageChange = (page: number) => { - setCurrentPage(Math.max(1, Math.min(page, totalPages))); + state.setCurrentPage(Math.max(1, Math.min(page, totalPages))); }; const handlePageSizeChange = (newPageSize: number) => { - setPageSize(newPageSize); - setCurrentPage(1); // Reset to first page when changing page size + state.setPageSize(newPageSize); }; - // Reset to first page when dataset changes - useEffect(() => { - setCurrentPage(1); - }, [totalRows]); - return (
{/* Pagination Controls - Fixed outside scrollable area */} @@ -65,7 +55,7 @@ export const EvaluationTable = observer(() => {
onUpdate({ value: e.target.value })} + value={localValue} + onChange={onChange} + onKeyDown={onKeyDown} + onBlur={onBlur} placeholder="Value" className={`${commonStyles.input.base} ${commonStyles.input.size.sm} ${commonStyles.width.sm}`} style={{ boxShadow: commonStyles.input.shadow }}