From 5f41c4a9431a9476fb171759c690a41e17a69d11 Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Date: Sun, 14 Dec 2025 14:46:28 +0300 Subject: [PATCH] Enhance dialog components by adding smooth scroll to top functionality upon opening; update ConfirmDialog, ClearSubmissionsDialog, and DuplicateChainDialog for improved user experience. Remove unused ConfirmDialog from ChainsListPage and TasksListPage, streamlining code. --- src/components/ClearSubmissionsDialog.tsx | 12 ++++++++-- src/components/ConfirmDialog.tsx | 12 ++++++++-- src/components/DuplicateChainDialog.tsx | 12 ++++++++-- src/pages/chains/ChainsListPage.tsx | 27 ++++++++--------------- src/pages/tasks/TasksListPage.tsx | 27 ++++++++--------------- 5 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/components/ClearSubmissionsDialog.tsx b/src/components/ClearSubmissionsDialog.tsx index b9ffe18..6e2934f 100644 --- a/src/components/ClearSubmissionsDialog.tsx +++ b/src/components/ClearSubmissionsDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { DialogRoot, @@ -29,6 +29,13 @@ export const ClearSubmissionsDialog: React.FC = ({ const { t } = useTranslation() const [clearSubmissions, { isLoading }] = useClearChainSubmissionsMutation() + // Прокручиваем страницу к началу при открытии диалога + useEffect(() => { + if (isOpen) { + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + }, [isOpen]) + const handleConfirm = async () => { if (!chain) return @@ -52,7 +59,7 @@ export const ClearSubmissionsDialog: React.FC = ({ if (!chain) return null return ( - !e.open && onClose()}> + !e.open && onClose()} scrollBehavior="inside"> {t('challenge.admin.chains.clear.submissions.dialog.title')} @@ -77,3 +84,4 @@ export const ClearSubmissionsDialog: React.FC = ({ + diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx index e1310ba..011072b 100644 --- a/src/components/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { DialogRoot, @@ -36,8 +36,16 @@ export const ConfirmDialog: React.FC = ({ const confirm = confirmLabel || t('challenge.admin.common.confirm') const cancel = cancelLabel || t('challenge.admin.common.cancel') + + // Прокручиваем страницу к началу при открытии диалога + useEffect(() => { + if (isOpen) { + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + }, [isOpen]) + return ( - !e.open && onClose()}> + !e.open && onClose()} scrollBehavior="inside"> {title} diff --git a/src/components/DuplicateChainDialog.tsx b/src/components/DuplicateChainDialog.tsx index ef6fb3c..891b114 100644 --- a/src/components/DuplicateChainDialog.tsx +++ b/src/components/DuplicateChainDialog.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { DialogRoot, @@ -33,6 +33,13 @@ export const DuplicateChainDialog: React.FC = ({ const [name, setName] = useState('') const [duplicateChain, { isLoading }] = useDuplicateChainMutation() + // Прокручиваем страницу к началу при открытии диалога + useEffect(() => { + if (isOpen) { + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + }, [isOpen]) + const handleClose = () => { setName('') onClose() @@ -68,7 +75,7 @@ export const DuplicateChainDialog: React.FC = ({ }) return ( - !e.open && handleClose()}> + !e.open && handleClose()} scrollBehavior="inside"> {t('challenge.admin.chains.duplicate.dialog.title')} @@ -106,3 +113,4 @@ export const DuplicateChainDialog: React.FC = ({ + diff --git a/src/pages/chains/ChainsListPage.tsx b/src/pages/chains/ChainsListPage.tsx index 3b67881..482aa4f 100644 --- a/src/pages/chains/ChainsListPage.tsx +++ b/src/pages/chains/ChainsListPage.tsx @@ -17,7 +17,6 @@ import { URLs } from '../../__data__/urls' import { LoadingSpinner } from '../../components/LoadingSpinner' import { ErrorAlert } from '../../components/ErrorAlert' import { EmptyState } from '../../components/EmptyState' -import { ConfirmDialog } from '../../components/ConfirmDialog' import { DuplicateChainDialog } from '../../components/DuplicateChainDialog' import { ClearSubmissionsDialog } from '../../components/ClearSubmissionsDialog' import type { ChallengeChain } from '../../types/challenge' @@ -27,26 +26,28 @@ export const ChainsListPage: React.FC = () => { const navigate = useNavigate() const { t } = useTranslation() const { data: chains, isLoading, error, refetch } = useGetChainsQuery() - const [deleteChain, { isLoading: isDeleting }] = useDeleteChainMutation() + const [deleteChain] = useDeleteChainMutation() const [searchQuery, setSearchQuery] = useState('') - const [chainToDelete, setChainToDelete] = useState(null) const [chainToDuplicate, setChainToDuplicate] = useState(null) const [chainToClearSubmissions, setChainToClearSubmissions] = useState(null) const [updatingChainId, setUpdatingChainId] = useState(null) const [updateChain] = useUpdateChainMutation() - const handleDeleteChain = async () => { - if (!chainToDelete) return + const handleDeleteChain = async (chain: ChallengeChain) => { + const confirmed = window.confirm( + t('challenge.admin.chains.delete.confirm.message', { name: chain.name }) + ) + + if (!confirmed) return try { - await deleteChain(chainToDelete.id).unwrap() + await deleteChain(chain.id).unwrap() toaster.create({ title: t('challenge.admin.common.success'), description: t('challenge.admin.chains.deleted'), type: 'success', }) - setChainToDelete(null) } catch (err) { toaster.create({ title: t('challenge.admin.common.error'), @@ -205,7 +206,7 @@ export const ChainsListPage: React.FC = () => { size="sm" variant="ghost" colorPalette="red" - onClick={() => setChainToDelete(chain)} + onClick={() => handleDeleteChain(chain)} > {t('challenge.admin.chains.list.button.delete')} @@ -218,16 +219,6 @@ export const ChainsListPage: React.FC = () => { )} - setChainToDelete(null)} - onConfirm={handleDeleteChain} - title={t('challenge.admin.chains.delete.confirm.title')} - message={t('challenge.admin.chains.delete.confirm.message', { name: chainToDelete?.name })} - confirmLabel={t('challenge.admin.chains.delete.confirm.button')} - isLoading={isDeleting} - /> - setChainToDuplicate(null)} diff --git a/src/pages/tasks/TasksListPage.tsx b/src/pages/tasks/TasksListPage.tsx index ecb61c7..7035a17 100644 --- a/src/pages/tasks/TasksListPage.tsx +++ b/src/pages/tasks/TasksListPage.tsx @@ -17,7 +17,6 @@ import { URLs } from '../../__data__/urls' import { LoadingSpinner } from '../../components/LoadingSpinner' import { ErrorAlert } from '../../components/ErrorAlert' import { EmptyState } from '../../components/EmptyState' -import { ConfirmDialog } from '../../components/ConfirmDialog' import type { ChallengeTask } from '../../types/challenge' import { toaster } from '../../components/ui/toaster' @@ -25,22 +24,24 @@ export const TasksListPage: React.FC = () => { const navigate = useNavigate() const { t } = useTranslation() const { data: tasks, isLoading, error, refetch } = useGetTasksQuery() - const [deleteTask, { isLoading: isDeleting }] = useDeleteTaskMutation() + const [deleteTask] = useDeleteTaskMutation() const [searchQuery, setSearchQuery] = useState('') - const [taskToDelete, setTaskToDelete] = useState(null) - const handleDeleteTask = async () => { - if (!taskToDelete) return + const handleDeleteTask = async (task: ChallengeTask) => { + const confirmed = window.confirm( + t('challenge.admin.tasks.delete.confirm.message', { title: task.title }) + ) + + if (!confirmed) return try { - await deleteTask(taskToDelete.id).unwrap() + await deleteTask(task.id).unwrap() toaster.create({ title: t('challenge.admin.common.success'), description: t('challenge.admin.tasks.deleted'), type: 'success', }) - setTaskToDelete(null) } catch (_err) { toaster.create({ title: t('challenge.admin.common.error'), @@ -152,7 +153,7 @@ export const TasksListPage: React.FC = () => { size="sm" variant="ghost" colorPalette="red" - onClick={() => setTaskToDelete(task)} + onClick={() => handleDeleteTask(task)} > {t('challenge.admin.tasks.list.button.delete')} @@ -164,16 +165,6 @@ export const TasksListPage: React.FC = () => { )} - - setTaskToDelete(null)} - onConfirm={handleDeleteTask} - title={t('challenge.admin.tasks.delete.confirm.title')} - message={t('challenge.admin.tasks.delete.confirm.message', { title: taskToDelete?.title })} - confirmLabel={t('challenge.admin.tasks.delete.confirm.button')} - isLoading={isDeleting} - /> ) }