mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 18:50:49 +08:00
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:
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "作者"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user