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

View File

@@ -327,6 +327,9 @@
"search_placeholder": "Filter by name, type, or provider", "search_placeholder": "Filter by name, type, or provider",
"page_size_label": "Per page", "page_size_label": "Per page",
"page_size_unit": "items", "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_all": "All",
"filter_qwen": "Qwen", "filter_qwen": "Qwen",
"filter_gemini": "Gemini", "filter_gemini": "Gemini",
@@ -709,7 +712,8 @@
"quota_management": { "quota_management": {
"title": "Quota Management", "title": "Quota Management",
"description": "Monitor OAuth quota status for Antigravity, Codex, and Gemini CLI credentials.", "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": { "system_info": {
"title": "Management Center Info", "title": "Management Center Info",

View File

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

View File

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