fix(webui): show import refresh timestamp

This commit is contained in:
Antti Kettunen 2026-05-16 18:08:05 +03:00
parent 7853d0cfb8
commit 3fdc815794
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
3 changed files with 43 additions and 8 deletions

View file

@ -63,6 +63,11 @@
color: #fff; color: #fff;
} }
.importPageRefreshBtnRefreshing {
opacity: 0.72;
cursor: progress;
}
.importPageStagingBar { .importPageStagingBar {
display: flex; display: flex;
align-items: center; align-items: center;
@ -88,6 +93,11 @@
white-space: nowrap; white-space: nowrap;
} }
.importStagingRefreshAt {
white-space: nowrap;
color: rgba(255, 255, 255, 0.4);
}
/* Tab Bar */ /* Tab Bar */
.importPageTabBar { .importPageTabBar {
display: flex; display: flex;

View file

@ -1,9 +1,9 @@
import { Link, Outlet } from '@tanstack/react-router'; import { Link, Outlet } from '@tanstack/react-router';
import { Show } from '@/components/primitives';
import { useReactPageShell } from '@/platform/shell/route-controllers'; import { useReactPageShell } from '@/platform/shell/route-controllers';
import type { ImportQueueEntry } from '../-import.types'; import type { ImportQueueEntry } from '../-import.types';
import styles from './import-page.module.css';
import { import {
getQueueProgressPercent, getQueueProgressPercent,
@ -11,12 +11,16 @@ import {
getStagingStatsText, getStagingStatsText,
} from '../-import.helpers'; } from '../-import.helpers';
import { useImportQueueWorkflow } from '../-import.store'; import { useImportQueueWorkflow } from '../-import.store';
import styles from './import-page.module.css';
import { fallbackImage, RefreshIcon, useImportStaging } from './import-shared'; import { fallbackImage, RefreshIcon, useImportStaging } from './import-shared';
export function ImportPage() { export function ImportPage() {
useReactPageShell('import'); useReactPageShell('import');
const { refreshStaging, stagingFiles, stagingPath, stagingQuery } = useImportStaging(); const { refreshStaging, stagingFiles, stagingPath, stagingQuery } = useImportStaging();
const isRefreshing = stagingQuery.isRefetching;
const lastRefreshedAt =
stagingQuery.dataUpdatedAt > 0 ? formatShortTime(stagingQuery.dataUpdatedAt) : null;
return ( return (
<div id="import-page" data-testid="import-page"> <div id="import-page" data-testid="import-page">
@ -26,6 +30,8 @@ export function ImportPage() {
fileCountText={getStagingStatsText(stagingFiles)} fileCountText={getStagingStatsText(stagingFiles)}
loading={stagingQuery.isLoading} loading={stagingQuery.isLoading}
stagingPath={stagingPath} stagingPath={stagingPath}
refreshing={isRefreshing}
lastRefreshedAt={lastRefreshedAt}
onRefresh={refreshStaging} onRefresh={refreshStaging}
/> />
<ImportProcessingQueue /> <ImportProcessingQueue />
@ -38,17 +44,29 @@ export function ImportPage() {
); );
} }
function formatShortTime(timestamp: number) {
return new Date(timestamp).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
}
function ImportHeader({ function ImportHeader({
error, error,
fileCountText, fileCountText,
loading, loading,
stagingPath, stagingPath,
refreshing,
lastRefreshedAt,
onRefresh, onRefresh,
}: { }: {
error: unknown; error: unknown;
fileCountText: string; fileCountText: string;
loading: boolean; loading: boolean;
stagingPath: string; stagingPath: string;
refreshing: boolean;
lastRefreshedAt: string | null;
onRefresh: () => void; onRefresh: () => void;
}) { }) {
return ( return (
@ -60,18 +78,27 @@ function ImportHeader({
</h1> </h1>
<button <button
type="button" type="button"
className={styles.importPageRefreshBtn} className={`${styles.importPageRefreshBtn} ${
refreshing ? styles.importPageRefreshBtnRefreshing : ''
}`}
title="Re-scan import folder" title="Re-scan import folder"
aria-busy={refreshing}
disabled={refreshing}
onClick={onRefresh} onClick={onRefresh}
> >
<RefreshIcon /> <RefreshIcon />
Refresh {refreshing ? 'Refreshing...' : 'Refresh'}
</button> </button>
</div> </div>
<div className={styles.importPageStagingBar} id="import-staging-bar"> <div className={styles.importPageStagingBar} id="import-staging-bar">
<span className={styles.importStagingPath} id="import-page-staging-path"> <span className={styles.importStagingPath} id="import-page-staging-path">
{error ? 'Import folder: error' : `Import: ${stagingPath}`} {error ? 'Import folder: error' : `Import: ${stagingPath}`}
</span> </span>
<Show when={lastRefreshedAt != null}>
<span className={styles.importStagingRefreshAt}>
{lastRefreshedAt ? `Last refreshed: ${lastRefreshedAt}` : null}
</span>
</Show>
<span className={styles.importStagingStats} id="import-page-staging-stats"> <span className={styles.importStagingStats} id="import-page-staging-stats">
{loading ? 'loading...' : fileCountText} {loading ? 'loading...' : fileCountText}
</span> </span>
@ -129,9 +156,7 @@ function ImportQueueItem({ entry }: { entry: ImportQueueEntry }) {
onError={fallbackImage} onError={fallbackImage}
/> />
) : ( ) : (
<div className={`${styles.importPageQueueArt} ${styles.importPageQueueArtEmpty}`}> <div className={`${styles.importPageQueueArt} ${styles.importPageQueueArtEmpty}`}>A</div>
A
</div>
)} )}
<div className={styles.importPageQueueInfo}> <div className={styles.importPageQueueInfo}>
<div className={styles.importPageQueueName}>{entry.label}</div> <div className={styles.importPageQueueName}>{entry.label}</div>

View file

@ -19,9 +19,9 @@ export function useImportStaging() {
}); });
return { return {
refreshStaging: () => { refreshStaging: async () => {
clearFinishedJobs(); clearFinishedJobs();
void invalidateImportStagingQueries(queryClient); await invalidateImportStagingQueries(queryClient);
}, },
stagingFiles: stagingQuery.data?.files ?? [], stagingFiles: stagingQuery.data?.files ?? [],
stagingPath: stagingQuery.data?.staging_path || 'Not configured', stagingPath: stagingQuery.data?.staging_path || 'Not configured',