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.

This commit is contained in:
2025-12-14 14:46:28 +03:00
parent 1d364a2351
commit 5f41c4a943
5 changed files with 48 additions and 42 deletions

View File

@@ -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<ClearSubmissionsDialogProps> = ({
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<ClearSubmissionsDialogProps> = ({
if (!chain) return null
return (
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()}>
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()} scrollBehavior="inside">
<DialogContent>
<DialogHeader>
<DialogTitle>{t('challenge.admin.chains.clear.submissions.dialog.title')}</DialogTitle>
@@ -77,3 +84,4 @@ export const ClearSubmissionsDialog: React.FC<ClearSubmissionsDialogProps> = ({

View File

@@ -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<ConfirmDialogProps> = ({
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 (
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()}>
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()} scrollBehavior="inside">
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>

View File

@@ -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<DuplicateChainDialogProps> = ({
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<DuplicateChainDialogProps> = ({
})
return (
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && handleClose()}>
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && handleClose()} scrollBehavior="inside">
<DialogContent>
<DialogHeader>
<DialogTitle>{t('challenge.admin.chains.duplicate.dialog.title')}</DialogTitle>
@@ -106,3 +113,4 @@ export const DuplicateChainDialog: React.FC<DuplicateChainDialogProps> = ({

View File

@@ -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<ChallengeChain | null>(null)
const [chainToDuplicate, setChainToDuplicate] = useState<ChallengeChain | null>(null)
const [chainToClearSubmissions, setChainToClearSubmissions] = useState<ChallengeChain | null>(null)
const [updatingChainId, setUpdatingChainId] = useState<string | null>(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')}
</Button>
@@ -218,16 +219,6 @@ export const ChainsListPage: React.FC = () => {
</Box>
)}
<ConfirmDialog
isOpen={!!chainToDelete}
onClose={() => 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}
/>
<DuplicateChainDialog
isOpen={!!chainToDuplicate}
onClose={() => setChainToDuplicate(null)}

View File

@@ -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<ChallengeTask | null>(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')}
</Button>
@@ -164,16 +165,6 @@ export const TasksListPage: React.FC = () => {
</Table.Root>
</Box>
)}
<ConfirmDialog
isOpen={!!taskToDelete}
onClose={() => 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}
/>
</Box>
)
}