feat(AuthFiles): add disabled filter option and enhance delete confirmation messages

This commit is contained in:
Supra4E8C
2026-04-25 01:59:26 +08:00
Unverified
parent 03186e447d
commit 2f2a571d23
7 changed files with 115 additions and 26 deletions
@@ -16,8 +16,10 @@ import {
type DeleteAllOptions = {
filter: string;
problemOnly: boolean;
disabledOnly: boolean;
onResetFilterToAll: () => void;
onResetProblemOnly: () => void;
onResetDisabledOnly: () => void;
};
export type UseAuthFilesDataResult = {
@@ -275,17 +277,28 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
const handleDeleteAll = useCallback(
(deleteAllOptions: DeleteAllOptions) => {
const { filter, problemOnly, onResetFilterToAll, onResetProblemOnly } = deleteAllOptions;
const {
filter,
problemOnly,
disabledOnly,
onResetFilterToAll,
onResetProblemOnly,
onResetDisabledOnly,
} = deleteAllOptions;
const isFiltered = filter !== 'all';
const isProblemOnly = problemOnly === true;
const isDisabledOnly = disabledOnly === true;
const typeLabel = isFiltered ? getTypeLabel(t, filter) : t('auth_files.filter_all');
const confirmMessage = isProblemOnly
? isFiltered
let confirmMessage = t('auth_files.delete_all_confirm');
if (isDisabledOnly) {
confirmMessage = t('auth_files.delete_filtered_result_confirm');
} else if (isProblemOnly) {
confirmMessage = isFiltered
? t('auth_files.delete_problem_filtered_confirm', { type: typeLabel })
: t('auth_files.delete_problem_confirm')
: isFiltered
? t('auth_files.delete_filtered_confirm', { type: typeLabel })
: t('auth_files.delete_all_confirm');
: t('auth_files.delete_problem_confirm');
} else if (isFiltered) {
confirmMessage = t('auth_files.delete_filtered_confirm', { type: typeLabel });
}
showConfirmation({
title: t('auth_files.delete_all_title', { defaultValue: 'Delete All Files' }),
@@ -295,7 +308,7 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
onConfirm: async () => {
setDeletingAll(true);
try {
if (!isFiltered && !isProblemOnly) {
if (!isFiltered && !isProblemOnly && !isDisabledOnly) {
await authFilesApi.deleteAll();
showNotification(t('auth_files.delete_all_success'), 'success');
setFiles((prev) => prev.filter((file) => isRuntimeOnlyAuthFile(file)));
@@ -305,15 +318,19 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
if (isRuntimeOnlyAuthFile(file)) return false;
if (isFiltered && file.type !== filter) return false;
if (isProblemOnly && !hasAuthFileStatusMessage(file)) return false;
if (isDisabledOnly && file.disabled !== true) return false;
return true;
});
if (filesToDelete.length === 0) {
const emptyMessage = isProblemOnly
? isFiltered
let emptyMessage = t('auth_files.delete_filtered_none', { type: typeLabel });
if (isDisabledOnly) {
emptyMessage = t('auth_files.delete_filtered_result_none');
} else if (isProblemOnly) {
emptyMessage = isFiltered
? t('auth_files.delete_problem_filtered_none', { type: typeLabel })
: t('auth_files.delete_problem_none')
: t('auth_files.delete_filtered_none', { type: typeLabel });
: t('auth_files.delete_problem_none');
}
showNotification(emptyMessage, 'info');
setDeletingAll(false);
return;
@@ -327,7 +344,12 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
applyDeletedFiles(result.files);
if (failed === 0 && isProblemOnly) {
if (failed === 0 && isDisabledOnly) {
showNotification(
t('auth_files.delete_filtered_result_success', { count: success }),
'success'
);
} else if (failed === 0 && isProblemOnly) {
showNotification(
isFiltered
? t('auth_files.delete_problem_filtered_success', {
@@ -342,6 +364,11 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
t('auth_files.delete_filtered_success', { count: success, type: typeLabel }),
'success'
);
} else if (isDisabledOnly) {
showNotification(
t('auth_files.delete_filtered_result_partial', { success, failed }),
'warning'
);
} else if (isProblemOnly) {
showNotification(
isFiltered
@@ -366,6 +393,9 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
if (isProblemOnly) {
onResetProblemOnly();
}
if (isDisabledOnly) {
onResetDisabledOnly();
}
}
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : '';
+1
View File
@@ -5,6 +5,7 @@ export type AuthFilesSortMode = (typeof AUTH_FILES_SORT_MODES)[number];
export type AuthFilesUiState = {
filter?: string;
problemOnly?: boolean;
disabledOnly?: boolean;
compactMode?: boolean;
search?: string;
page?: number;
+6
View File
@@ -501,6 +501,8 @@
"delete_problem_button_with_type": "Delete Problematic {{type}} Files",
"delete_problem_confirm": "Are you sure you want to delete all problematic auth files? This operation cannot be undone!",
"delete_problem_filtered_confirm": "Are you sure you want to delete all problematic {{type}} auth files? This operation cannot be undone!",
"delete_filtered_result_button": "Delete filtered results",
"delete_filtered_result_confirm": "Are you sure you want to delete auth files in the current filtered results? This operation cannot be undone!",
"upload_error_json": "Only JSON files are allowed",
"upload_error_size": "File size cannot exceed {{maxSize}}",
"upload_success": "File uploaded successfully",
@@ -516,6 +518,9 @@
"delete_problem_filtered_partial": "Problematic {{type}} auth files deletion finished: {{success}} succeeded, {{failed}} failed",
"delete_problem_none": "No deletable problematic auth files under the current filter",
"delete_problem_filtered_none": "No deletable problematic {{type}} auth files under the current filter",
"delete_filtered_result_success": "Deleted {{count}} auth files from the filtered results successfully",
"delete_filtered_result_partial": "Filtered result deletion finished: {{success}} succeeded, {{failed}} failed",
"delete_filtered_result_none": "No deletable auth files in the current filtered results",
"files_count": "files",
"pagination_prev": "Previous",
"pagination_next": "Next",
@@ -524,6 +529,7 @@
"search_placeholder": "Filter by name, type, or provider. Use * as a wildcard",
"problem_filter_label": "Problem Filter",
"problem_filter_only": "Only show problematic credentials",
"disabled_filter_only": "Only show disabled credentials",
"display_options_label": "Display options",
"compact_mode_label": "Compact mode",
"sort_label": "Sort",
+6
View File
@@ -501,6 +501,8 @@
"delete_problem_button_with_type": "Удалить проблемные файлы {{type}}",
"delete_problem_confirm": "Удалить все проблемные файлы авторизации? Это действие нельзя отменить!",
"delete_problem_filtered_confirm": "Удалить все проблемные файлы авторизации {{type}}? Это действие нельзя отменить!",
"delete_filtered_result_button": "Удалить результаты фильтра",
"delete_filtered_result_confirm": "Удалить файлы авторизации из текущих результатов фильтра? Это действие нельзя отменить!",
"upload_error_json": "Допустимы только файлы JSON",
"upload_error_size": "Размер файла не может превышать {{maxSize}}",
"upload_success": "Файл успешно загружен",
@@ -516,6 +518,9 @@
"delete_problem_filtered_partial": "Удаление проблемных файлов авторизации {{type}} завершено: успешных {{success}}, ошибок {{failed}}",
"delete_problem_none": "Нет проблемных файлов авторизации для удаления при текущем фильтре",
"delete_problem_filtered_none": "Нет проблемных файлов авторизации {{type}} для удаления при текущем фильтре",
"delete_filtered_result_success": "Удалено файлов авторизации из результатов фильтра: {{count}}",
"delete_filtered_result_partial": "Удаление результатов фильтра завершено: успешных {{success}}, ошибок {{failed}}",
"delete_filtered_result_none": "Нет файлов авторизации для удаления в текущих результатах фильтра",
"files_count": "файлов",
"pagination_prev": "Предыдущая",
"pagination_next": "Следующая",
@@ -524,6 +529,7 @@
"search_placeholder": "Фильтр по имени, типу или провайдеру, поддерживается wildcard *",
"problem_filter_label": "Фильтр проблем",
"problem_filter_only": "Показывать только проблемные учётные данные",
"disabled_filter_only": "Показывать только отключённые учётные данные",
"display_options_label": "Параметры отображения",
"compact_mode_label": "Компактный режим",
"sort_label": "Сортировка",
+6
View File
@@ -501,6 +501,8 @@
"delete_problem_button_with_type": "删除 {{type}} 问题凭证",
"delete_problem_confirm": "确定要删除所有有问题的认证文件吗?此操作不可恢复!",
"delete_problem_filtered_confirm": "确定要删除筛选出的有问题的 {{type}} 认证文件吗?此操作不可恢复!",
"delete_filtered_result_button": "删除筛选结果",
"delete_filtered_result_confirm": "确定要删除当前筛选结果中的认证文件吗?此操作不可恢复!",
"upload_error_json": "只能上传JSON文件",
"upload_error_size": "文件大小不能超过 {{maxSize}}",
"upload_success": "文件上传成功",
@@ -516,6 +518,9 @@
"delete_problem_filtered_partial": "有问题的 {{type}} 认证文件删除完成,成功 {{success}} 个,失败 {{failed}} 个",
"delete_problem_none": "当前没有可删除的有问题认证文件",
"delete_problem_filtered_none": "当前筛选类型 ({{type}}) 下没有可删除的有问题认证文件",
"delete_filtered_result_success": "成功删除 {{count}} 个筛选结果中的认证文件",
"delete_filtered_result_partial": "筛选结果删除完成,成功 {{success}} 个,失败 {{failed}} 个",
"delete_filtered_result_none": "当前筛选结果中没有可删除的认证文件",
"files_count": "个文件",
"pagination_prev": "上一页",
"pagination_next": "下一页",
@@ -524,6 +529,7 @@
"search_placeholder": "输入名称、类型或提供方关键字,支持 * 通配",
"problem_filter_label": "问题筛选",
"problem_filter_only": "仅显示有问题凭证",
"disabled_filter_only": "仅显示已停用凭证",
"display_options_label": "显示选项",
"compact_mode_label": "简略模式",
"sort_label": "排序",
+6
View File
@@ -501,6 +501,8 @@
"delete_problem_button_with_type": "刪除 {{type}} 問題憑證",
"delete_problem_confirm": "確定要刪除所有有問題的驗證檔案嗎?此操作無法還原!",
"delete_problem_filtered_confirm": "確定要刪除篩選出的有問題的 {{type}} 驗證檔案嗎?此操作無法還原!",
"delete_filtered_result_button": "刪除篩選結果",
"delete_filtered_result_confirm": "確定要刪除目前篩選結果中的驗證檔案嗎?此操作無法還原!",
"upload_error_json": "只能上傳 JSON 檔案",
"upload_error_size": "檔案大小不能超過 {{maxSize}}",
"upload_success": "檔案上傳成功",
@@ -516,6 +518,9 @@
"delete_problem_filtered_partial": "有問題的 {{type}} 驗證檔案刪除完成,成功 {{success}} 個,失敗 {{failed}} 個",
"delete_problem_none": "目前沒有可刪除的有問題驗證檔案",
"delete_problem_filtered_none": "目前篩選類型({{type}})下沒有可刪除的有問題驗證檔案",
"delete_filtered_result_success": "成功刪除 {{count}} 個篩選結果中的驗證檔案",
"delete_filtered_result_partial": "篩選結果刪除完成,成功 {{success}} 個,失敗 {{failed}} 個",
"delete_filtered_result_none": "目前篩選結果中沒有可刪除的驗證檔案",
"files_count": "個檔案",
"pagination_prev": "上一頁",
"pagination_next": "下一頁",
@@ -524,6 +529,7 @@
"search_placeholder": "輸入名稱、類型或供應方關鍵字,支援 * 萬用字元",
"problem_filter_label": "問題篩選",
"problem_filter_only": "僅顯示有問題憑證",
"disabled_filter_only": "僅顯示已停用憑證",
"display_options_label": "顯示選項",
"compact_mode_label": "簡略模式",
"sort_label": "排序",
+47 -13
View File
@@ -88,6 +88,7 @@ export function AuthFilesPage() {
const [filter, setFilter] = useState<'all' | string>('all');
const [problemOnly, setProblemOnly] = useState(false);
const [disabledOnly, setDisabledOnly] = useState(false);
const [compactMode, setCompactMode] = useState(false);
const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
@@ -201,6 +202,9 @@ export function AuthFilesPage() {
if (typeof persisted.problemOnly === 'boolean') {
setProblemOnly(persisted.problemOnly);
}
if (typeof persisted.disabledOnly === 'boolean') {
setDisabledOnly(persisted.disabledOnly);
}
if (
typeof persistedCompactMode !== 'boolean' &&
typeof persisted.compactMode === 'boolean'
@@ -243,6 +247,7 @@ export function AuthFilesPage() {
writeAuthFilesUiState({
filter,
problemOnly,
disabledOnly,
compactMode,
search,
page,
@@ -254,6 +259,7 @@ export function AuthFilesPage() {
writePersistedAuthFilesCompactMode(compactMode);
}, [
compactMode,
disabledOnly,
filter,
page,
pageSize,
@@ -354,9 +360,14 @@ export function AuthFilesPage() {
return Array.from(types);
}, [files]);
const filesMatchingProblemFilter = useMemo(
() => (problemOnly ? files.filter(hasAuthFileStatusMessage) : files),
[files, problemOnly]
const filesMatchingStatusFilters = useMemo(
() =>
files.filter((file) => {
if (problemOnly && !hasAuthFileStatusMessage(file)) return false;
if (disabledOnly && file.disabled !== true) return false;
return true;
}),
[disabledOnly, files, problemOnly]
);
const sortOptions = useMemo(
@@ -369,13 +380,13 @@ export function AuthFilesPage() {
);
const typeCounts = useMemo(() => {
const counts: Record<string, number> = { all: filesMatchingProblemFilter.length };
filesMatchingProblemFilter.forEach((file) => {
const counts: Record<string, number> = { all: filesMatchingStatusFilters.length };
filesMatchingStatusFilters.forEach((file) => {
if (!file.type) return;
counts[file.type] = (counts[file.type] || 0) + 1;
});
return counts;
}, [filesMatchingProblemFilter]);
}, [filesMatchingStatusFilters]);
const normalizedSearch = search.trim();
const wildcardSearch = useMemo(() => buildWildcardSearch(normalizedSearch), [normalizedSearch]);
@@ -383,7 +394,7 @@ export function AuthFilesPage() {
const filtered = useMemo(() => {
const normalizedTerm = normalizedSearch.toLowerCase();
return filesMatchingProblemFilter.filter((item) => {
return filesMatchingStatusFilters.filter((item) => {
const matchType = filter === 'all' || item.type === filter;
const matchSearch =
!normalizedSearch ||
@@ -395,7 +406,7 @@ export function AuthFilesPage() {
});
return matchType && matchSearch;
});
}, [filesMatchingProblemFilter, filter, normalizedSearch, wildcardSearch]);
}, [filesMatchingStatusFilters, filter, normalizedSearch, wildcardSearch]);
const sorted = useMemo(() => {
const copy = [...filtered];
@@ -634,13 +645,19 @@ export function AuthFilesPage() {
</div>
);
const deleteAllButtonLabel = problemOnly
? filter === 'all'
? t('auth_files.delete_problem_button')
: t('auth_files.delete_problem_button_with_type', { type: getTypeLabel(t, filter) })
: filter === 'all'
const deleteAllButtonLabel = (() => {
if (disabledOnly) {
return t('auth_files.delete_filtered_result_button');
}
if (problemOnly) {
return filter === 'all'
? t('auth_files.delete_problem_button')
: t('auth_files.delete_problem_button_with_type', { type: getTypeLabel(t, filter) });
}
return filter === 'all'
? t('auth_files.delete_all_button')
: `${t('common.delete')} ${getTypeLabel(t, filter)}`;
})();
return (
<div className={styles.container}>
@@ -671,8 +688,10 @@ export function AuthFilesPage() {
handleDeleteAll({
filter,
problemOnly,
disabledOnly,
onResetFilterToAll: () => setFilter('all'),
onResetProblemOnly: () => setProblemOnly(false),
onResetDisabledOnly: () => setDisabledOnly(false),
})
}
disabled={disableControls || loading || deletingAll}
@@ -757,6 +776,21 @@ export function AuthFilesPage() {
}
/>
</div>
<div className={styles.filterToggleCard}>
<ToggleSwitch
checked={disabledOnly}
onChange={(value) => {
setDisabledOnly(value);
setPage(1);
}}
ariaLabel={t('auth_files.disabled_filter_only')}
label={
<span className={styles.filterToggleLabel}>
{t('auth_files.disabled_filter_only')}
</span>
}
/>
</div>
<div className={styles.filterToggleCard}>
<ToggleSwitch
checked={compactMode}