feat(quota): enhance QuotaSection with improved view mode handling and refresh functionality

- Introduced effective view mode logic to manage 'paged' and 'all' views based on file count.
- Added a warning for too many files when in 'all' view, prompting users to switch to 'paged'.
- Updated refresh button to handle loading states more effectively and provide clearer user feedback.
- Enhanced UI with new translations for view modes and refresh actions.
- Adjusted styles for better alignment and spacing in the view mode toggle and refresh button.
This commit is contained in:
moxi
2026-01-04 00:45:34 +08:00
parent 334d75f2dd
commit 38a3e20427
4 changed files with 71 additions and 25 deletions

View File

@@ -2,11 +2,12 @@
* Generic quota section component.
*/
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { EmptyState } from '@/components/ui/EmptyState';
import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { useQuotaStore, useThemeStore } from '@/stores';
import type { AuthFileItem, ResolvedTheme } from '@/types';
import { QuotaCard } from './QuotaCard';
@@ -116,6 +117,8 @@ export function QuotaSection<TState extends QuotaStatusState, TData>({
files,
config
]);
const showAllAllowed = filteredFiles.length <= MAX_SHOW_ALL_THRESHOLD;
const effectiveViewMode: ViewMode = viewMode === 'all' && !showAllAllowed ? 'paged' : viewMode;
const {
pageSize,
@@ -129,25 +132,55 @@ export function QuotaSection<TState extends QuotaStatusState, TData>({
setLoading
} = useQuotaPagination(filteredFiles);
useEffect(() => {
if (showAllAllowed) return;
if (viewMode !== 'all') return;
let cancelled = false;
queueMicrotask(() => {
if (cancelled) return;
setViewMode('paged');
setShowTooManyWarning(true);
});
return () => {
cancelled = true;
};
}, [showAllAllowed, viewMode]);
// Update page size based on view mode and columns
useEffect(() => {
if (viewMode === 'all') {
if (effectiveViewMode === 'all') {
setPageSize(Math.max(1, filteredFiles.length));
} else {
// Paged mode: 3 rows * columns
setPageSize(columns * 3);
}
}, [viewMode, columns, filteredFiles.length, setPageSize]);
}, [effectiveViewMode, columns, filteredFiles.length, setPageSize]);
const { quota, loadQuota } = useQuotaLoader(config);
const pendingQuotaRefreshRef = useRef(false);
const prevFilesLoadingRef = useRef(loading);
const handleRefresh = useCallback(() => {
if (viewMode === 'all') {
loadQuota(filteredFiles, 'all', setLoading);
} else {
loadQuota(pageItems, 'page', setLoading);
}
}, [loadQuota, filteredFiles, pageItems, viewMode, setLoading]);
pendingQuotaRefreshRef.current = true;
void triggerHeaderRefresh();
}, []);
useEffect(() => {
const wasLoading = prevFilesLoadingRef.current;
prevFilesLoadingRef.current = loading;
if (!pendingQuotaRefreshRef.current) return;
if (loading) return;
if (!wasLoading) return;
pendingQuotaRefreshRef.current = false;
const scope = effectiveViewMode === 'all' ? 'all' : 'page';
const targets = effectiveViewMode === 'all' ? filteredFiles : pageItems;
loadQuota(targets, scope, setLoading);
}, [loading, effectiveViewMode, filteredFiles, pageItems, loadQuota, setLoading]);
useEffect(() => {
if (loading) return;
@@ -185,14 +218,14 @@ export function QuotaSection<TState extends QuotaStatusState, TData>({
<div className={styles.headerActions}>
<div className={styles.viewModeToggle}>
<Button
variant={viewMode === 'paged' ? 'primary' : 'secondary'}
variant={effectiveViewMode === 'paged' ? 'primary' : 'secondary'}
size="sm"
onClick={() => setViewMode('paged')}
>
{t('auth_files.view_mode_paged')}
</Button>
<Button
variant={viewMode === 'all' ? 'primary' : 'secondary'}
variant={effectiveViewMode === 'all' ? 'primary' : 'secondary'}
size="sm"
onClick={() => {
if (filteredFiles.length > MAX_SHOW_ALL_THRESHOLD) {
@@ -206,14 +239,17 @@ export function QuotaSection<TState extends QuotaStatusState, TData>({
</Button>
</div>
<Button
variant="ghost"
variant="secondary"
size="sm"
onClick={handleRefresh}
disabled={disabled || sectionLoading || filteredFiles.length === 0}
loading={sectionLoading}
title={t(`${config.i18nPrefix}.refresh_button`)}
disabled={disabled || sectionLoading || loading || filteredFiles.length === 0}
loading={sectionLoading || loading}
title={t('quota_management.refresh_files_and_quota')}
>
{!sectionLoading && <IconRefreshCw size={18} />}
<span className={styles.refreshButtonContent}>
{!sectionLoading && !loading && <IconRefreshCw size={18} />}
{t('quota_management.refresh_files_and_quota')}
</span>
</Button>
</div>
}
@@ -239,7 +275,7 @@ export function QuotaSection<TState extends QuotaStatusState, TData>({
/>
))}
</div>
{filteredFiles.length > pageSize && viewMode === 'paged' && (
{filteredFiles.length > pageSize && effectiveViewMode === 'paged' && (
<div className={styles.pagination}>
<Button
variant="secondary"

View File

@@ -327,6 +327,9 @@
"search_placeholder": "Filter by name, type, or provider",
"page_size_label": "Per page",
"page_size_unit": "items",
"view_mode_paged": "Paged",
"view_mode_all": "Show all",
"too_many_files_warning": "Too many credentials. Showing all may cause performance issues, please use paged view.",
"filter_all": "All",
"filter_qwen": "Qwen",
"filter_gemini": "Gemini",
@@ -709,7 +712,8 @@
"quota_management": {
"title": "Quota Management",
"description": "Monitor OAuth quota status for Antigravity, Codex, and Gemini CLI credentials.",
"refresh_files": "Refresh auth files"
"refresh_files": "Refresh auth files",
"refresh_files_and_quota": "Refresh auth files & page quota"
},
"system_info": {
"title": "Management Center Info",

View File

@@ -712,7 +712,8 @@
"quota_management": {
"title": "配额管理",
"description": "集中查看 OAuth 额度与剩余情况",
"refresh_files": "刷新认证文件"
"refresh_files": "刷新认证文件",
"refresh_files_and_quota": "刷新认证文件列表&当前页额度"
},
"system_info": {
"title": "管理中心信息",
@@ -838,4 +839,4 @@
"version": "管理中心版本",
"author": "作者"
}
}
}

View File

@@ -132,10 +132,15 @@
.viewModeToggle {
display: flex;
gap: 8px;
background-color: var(--bg-secondary);
padding: 4px;
border-radius: $radius-md;
gap: $spacing-xs;
align-items: center;
}
.refreshButtonContent {
display: inline-flex;
align-items: center;
gap: $spacing-xs;
white-space: nowrap;
}
.antigravityCard {
@@ -379,4 +384,4 @@
font-size: 14px;
line-height: 1.6;
}
}
}