diff --git a/app/(user-data)/my-models/page.tsx b/app/(user-data)/my-models/page.tsx index c75294b3..e41b7974 100644 --- a/app/(user-data)/my-models/page.tsx +++ b/app/(user-data)/my-models/page.tsx @@ -48,6 +48,7 @@ import { trackJob, TrackedJob, } from '@/lib/api/jobTracker'; +import { parseWorkspaceDate } from '@/lib/utils/date'; interface MyModelItem { id: string; // Model name / filename @@ -518,7 +519,7 @@ export default function MyModelsPage() { headerName: 'Modification Date', width: 220, type: 'dateTime', - valueGetter: (_value, row) => (row.modDate ? new Date(row.modDate) : null), + valueGetter: (_value, row) => (row.modDate ? (parseWorkspaceDate(row.modDate) ?? new Date(row.modDate)) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, { diff --git a/app/(user-data)/myMedia/page.tsx b/app/(user-data)/myMedia/page.tsx index ea6c3416..0b3e2755 100644 --- a/app/(user-data)/myMedia/page.tsx +++ b/app/(user-data)/myMedia/page.tsx @@ -20,6 +20,7 @@ import { workspaceDelete } from '@/lib/api/workspace'; import { useAuth } from '@/components/auth/AuthProvider'; import DataControlHeader from '@/components/layout/DataControlHeader'; import ExportModal from '@/components/ui/ExportModal'; +import { parseWorkspaceDate } from '@/lib/utils/date'; interface MyMediaItem { id: string; @@ -143,7 +144,7 @@ export default function MyMediaPage() { headerName: 'Modification Date', width: 250, type: 'dateTime', - valueGetter: (_value, row) => (row.modDate ? new Date(row.modDate) : null), + valueGetter: (_value, row) => (row.modDate ? (parseWorkspaceDate(row.modDate) ?? new Date(row.modDate)) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, { diff --git a/app/data/[...path]/page.tsx b/app/data/[...path]/page.tsx index 67a862af..5af10ebb 100644 --- a/app/data/[...path]/page.tsx +++ b/app/data/[...path]/page.tsx @@ -34,6 +34,7 @@ import DownloadIcon from '@mui/icons-material/Download'; import SearchIcon from '@mui/icons-material/Search'; import NextLink from 'next/link'; import { workspaceLs, workspaceDownloadUrl } from '@/lib/api/workspace'; +import { parseWorkspaceDate } from '@/lib/utils/date'; import ShowMetadataDialog from '@/components/ui/ShowMetadataDialog'; interface WorkspaceItem { @@ -53,7 +54,8 @@ function parseWorkspaceLsEntry(entry: unknown[], parentPath: string): WorkspaceI const name = String(entry[0] || ''); const type = String(entry[1] || 'unknown'); const path = String(entry[2] || `${parentPath}/${name}`); - const timestamp = entry[3] ? new Date(String(entry[3])).toLocaleString() : ''; + const rawTs = String(entry[3] || ''); + const timestamp = rawTs; const size = Number(entry[6]) || 0; const owner = String(entry[5] || ''); const isFolder = type === 'folder' || type === 'modelfolder'; @@ -234,7 +236,7 @@ export default function DataBrowserPage({ params }: { params: Promise<{ path: st headerName: 'Modified', width: 160, type: 'dateTime', - valueGetter: (_value, row) => (row.modDate ? new Date(row.modDate) : null), + valueGetter: (_value, row) => (row.modDate ? (parseWorkspaceDate(row.modDate) ?? new Date(row.modDate)) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, { diff --git a/app/fba/[...path]/page.tsx b/app/fba/[...path]/page.tsx index c7f8d0ea..1176f855 100644 --- a/app/fba/[...path]/page.tsx +++ b/app/fba/[...path]/page.tsx @@ -192,8 +192,10 @@ function extractFbaObjectRefsFromLs(payload: Record): string[ if (type !== 'fba') continue; const ref = resolveWorkspaceLsRef(entry); if (!ref || isFbaContainerRef(ref)) continue; - const ts = Number.isFinite(new Date(String(entry[3] ?? '')).getTime()) - ? new Date(String(entry[3] ?? '')).getTime() + const rawTs = String(entry[3] ?? ''); + const normalizedTs = rawTs.replace(/^(\d{4}-\d{2}-\d{2})-(\d{2}:\d{2}:\d{2})$/, '$1T$2'); + const ts = Number.isFinite(new Date(normalizedTs).getTime()) + ? new Date(normalizedTs).getTime() : 0; refs.push({ ref, ts }); } diff --git a/app/model/[...path]/page.tsx b/app/model/[...path]/page.tsx index 6ad9fb6b..ac5199bb 100644 --- a/app/model/[...path]/page.tsx +++ b/app/model/[...path]/page.tsx @@ -52,6 +52,7 @@ import { trackJob, type TrackedJob, } from '@/lib/api/jobTracker'; +import { parseWorkspaceDate } from '@/lib/utils/date'; import ModelDetailHeader from '@/components/ui/ModelDetailHeader'; import type { FbaAdvancedOptions } from '@/components/ui/MediaSelectionDialog'; import DownloadModelMenu from '@/components/ui/DownloadModelMenu'; @@ -503,7 +504,7 @@ function buildTableConfig(model: Record): Record (row.timestamp ? new Date(String(row.timestamp)) : null), + valueGetter: (_value, row) => (row.timestamp ? (parseWorkspaceDate(String(row.timestamp)) ?? new Date(String(row.timestamp))) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, ], @@ -519,7 +520,7 @@ function buildTableConfig(model: Record): Record (row.rundate ? new Date(String(row.rundate)) : null), + valueGetter: (_value, row) => (row.rundate ? (parseWorkspaceDate(String(row.rundate)) ?? new Date(String(row.rundate))) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, ], @@ -562,7 +563,7 @@ function a11yProps(index: number) { function formatRelativeTimestamp(value: unknown): string { if (typeof value !== 'string' && typeof value !== 'number') return '-'; - const date = new Date(value); + const date = parseWorkspaceDate(String(value)) ?? new Date(value); if (Number.isNaN(date.getTime())) return String(value); return date.toLocaleString(); } @@ -859,7 +860,7 @@ function extractFbaRows( ref, objective: String(fba.objective ?? '-'), objectiveFunction: String(fba.objective_function ?? 'N/A'), - media: summarizeMediaRef(fba.media), + media: summarizeMediaRef(fba.media_ref ?? fba.media), timestamp: formatRelativeTimestamp(fba.timestamp ?? fba.rundate), }); } @@ -930,7 +931,7 @@ function extractGapfillRows( id, ref, integrated: (gapfill.integrated ?? gapfill.integrated_solution) ? 'Yes' : 'No', - media: summarizeMediaRef(gapfill.media), + media: summarizeMediaRef(gapfill.media_ref ?? gapfill.media), timestamp: formatRelativeTimestamp(gapfill.rundate ?? gapfill.timestamp), }); } @@ -1427,10 +1428,10 @@ export default function ModelDetailPage({ params }: { params: Promise<{ path: st error: modelEditsError, refetch: refetchModelEdits, } = useQuery({ - queryKey: ['modelEdits', USE_MODELSEED_API, workspaceCandidates[0]], - enabled: USE_MODELSEED_API && workspaceCandidates.length > 0, + queryKey: ['modelEdits', USE_MODELSEED_API, apiCandidates[0]], + enabled: USE_MODELSEED_API && apiCandidates.length > 0, queryFn: async () => { - const edits = await listModelEditsFromApi(workspaceCandidates[0]); + const edits = await listModelEditsFromApi(apiCandidates[0]); return Array.isArray(edits) ? edits : []; }, retry: 0, @@ -1821,7 +1822,7 @@ export default function ModelDetailPage({ params }: { params: Promise<{ path: st headerName: 'Timestamp', width: 220, type: 'dateTime', - valueGetter: (value) => (value ? new Date(String(value)) : null), + valueGetter: (value) => (value ? (parseWorkspaceDate(String(value)) ?? new Date(String(value))) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, { field: 'user', headerName: 'User', width: 220 }, @@ -1889,10 +1890,11 @@ export default function ModelDetailPage({ params }: { params: Promise<{ path: st headerName: 'ID', width: 180, renderCell: (params) => { - const fbaId = params.value; - const fbaHref = `/fba${workspacePath}/fba/${fbaId}`; + const ref = typeof params.row.ref === 'string' && params.row.ref + ? params.row.ref + : `${workspacePath}/fba/${String(params.value ?? '')}`; return ( - + {String(params.value ?? '')} ); @@ -1906,7 +1908,7 @@ export default function ModelDetailPage({ params }: { params: Promise<{ path: st headerName: 'Time', width: 180, type: 'dateTime', - valueGetter: (_value, row) => (row.timestamp ? new Date(String(row.timestamp)) : null), + valueGetter: (_value, row) => (row.timestamp ? (parseWorkspaceDate(String(row.timestamp)) ?? new Date(String(row.timestamp))) : null), valueFormatter: (value: Date | null) => (value ? value.toLocaleString() : '-'), }, ]; diff --git a/components/layout/DataControlHeader.tsx b/components/layout/DataControlHeader.tsx index a1b703e1..8f7f7f51 100644 --- a/components/layout/DataControlHeader.tsx +++ b/components/layout/DataControlHeader.tsx @@ -9,6 +9,7 @@ import { gridRowCountSelector, gridFilteredRowCountSelector, gridFilterModelSelector, + gridRowsLoadingSelector, type GridColDef, type GridFilterItem, type GridFilterModel, @@ -91,6 +92,8 @@ function CustomPagination() { // Use filtered row count so pagination shows correct total after client-side filtering const filteredCount = useGridSelector(apiRef, gridFilteredRowCountSelector); const rowCount = useGridSelector(apiRef, gridRowCountSelector); + // Hide pagination while data is loading to prevent "0-0 of 0" flash + const isLoading = useGridSelector(apiRef, gridRowsLoadingSelector); // Prefer filtered count (client-side filtering), fall back to total rowCount const displayCount = filteredCount ?? rowCount; const rowCountValue = displayCount ?? 0; @@ -107,7 +110,7 @@ function CustomPagination() { } }, [apiRef, ready, pageValue, pageSizeValue, lastPage]); - if (!ready) { + if (!ready || isLoading) { return null; } diff --git a/lib/api/modelseed.ts b/lib/api/modelseed.ts index f190a1d1..5d497b37 100644 --- a/lib/api/modelseed.ts +++ b/lib/api/modelseed.ts @@ -625,27 +625,7 @@ export async function listPublicMediaFromApi(): Promise * ``` */ export async function listMyMediaFromApi(): Promise { - const primary = await listMediaGeneric('/api/media/mine'); - if (primary.length > 0) return primary; - - if (!USE_NEW_PROXY) return primary; - const username = getStoredAuthUsername(); - if (!username) return primary; - - try { - const fallbackPaths = [ - `/${username}/media`, - `/${username}/modelseed/media`, - ]; - for (const path of fallbackPaths) { - const viaWorkspace = await listMediaViaWorkspaceLs(path); - if (viaWorkspace.length > 0) return viaWorkspace; - } - return primary; - } catch (err) { - console.warn('modelseed-api: fallback /api/workspace/ls media lookup failed:', err); - return primary; - } + return listMediaGeneric('/api/media/mine'); } /** diff --git a/lib/utils/date.ts b/lib/utils/date.ts new file mode 100644 index 00000000..00a8e849 --- /dev/null +++ b/lib/utils/date.ts @@ -0,0 +1,13 @@ +/** + * Normalize workspace date strings like "2026-04-14-17:46:54" to ISO format. + * Workspace API returns dates with dash between date and time instead of "T". + */ +export function parseWorkspaceDate(value: unknown): Date | null { + if (typeof value !== 'string' || !value) return null; + const normalized = value.replace( + /^(\d{4}-\d{2}-\d{2})-(\d{2}:\d{2}:\d{2})$/, + '$1T$2', + ); + const date = new Date(normalized); + return Number.isNaN(date.getTime()) ? null : date; +}