mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 11:10: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.
|
* 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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "管理中心信息",
|
||||||
@@ -838,4 +839,4 @@
|
|||||||
"version": "管理中心版本",
|
"version": "管理中心版本",
|
||||||
"author": "作者"
|
"author": "作者"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -379,4 +384,4 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user