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 { useTranslation } from 'react-i18next'
import { import {
DialogRoot, DialogRoot,
@@ -29,6 +29,13 @@ export const ClearSubmissionsDialog: React.FC<ClearSubmissionsDialogProps> = ({
const { t } = useTranslation() const { t } = useTranslation()
const [clearSubmissions, { isLoading }] = useClearChainSubmissionsMutation() const [clearSubmissions, { isLoading }] = useClearChainSubmissionsMutation()
// Прокручиваем страницу к началу при открытии диалога
useEffect(() => {
if (isOpen) {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}, [isOpen])
const handleConfirm = async () => { const handleConfirm = async () => {
if (!chain) return if (!chain) return
@@ -52,7 +59,7 @@ export const ClearSubmissionsDialog: React.FC<ClearSubmissionsDialogProps> = ({
if (!chain) return null if (!chain) return null
return ( return (
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()}> <DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()} scrollBehavior="inside">
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{t('challenge.admin.chains.clear.submissions.dialog.title')}</DialogTitle> <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 { useTranslation } from 'react-i18next'
import { import {
DialogRoot, DialogRoot,
@@ -36,8 +36,16 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
const confirm = confirmLabel || t('challenge.admin.common.confirm') const confirm = confirmLabel || t('challenge.admin.common.confirm')
const cancel = cancelLabel || t('challenge.admin.common.cancel') const cancel = cancelLabel || t('challenge.admin.common.cancel')
// Прокручиваем страницу к началу при открытии диалога
useEffect(() => {
if (isOpen) {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}, [isOpen])
return ( return (
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()}> <DialogRoot open={isOpen} onOpenChange={(e) => !e.open && onClose()} scrollBehavior="inside">
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{title}</DialogTitle> <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 { useTranslation } from 'react-i18next'
import { import {
DialogRoot, DialogRoot,
@@ -33,6 +33,13 @@ export const DuplicateChainDialog: React.FC<DuplicateChainDialogProps> = ({
const [name, setName] = useState('') const [name, setName] = useState('')
const [duplicateChain, { isLoading }] = useDuplicateChainMutation() const [duplicateChain, { isLoading }] = useDuplicateChainMutation()
// Прокручиваем страницу к началу при открытии диалога
useEffect(() => {
if (isOpen) {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}, [isOpen])
const handleClose = () => { const handleClose = () => {
setName('') setName('')
onClose() onClose()
@@ -68,7 +75,7 @@ export const DuplicateChainDialog: React.FC<DuplicateChainDialogProps> = ({
}) })
return ( return (
<DialogRoot open={isOpen} onOpenChange={(e) => !e.open && handleClose()}> <DialogRoot open={isOpen} onOpenChange={(e) => !e.open && handleClose()} scrollBehavior="inside">
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{t('challenge.admin.chains.duplicate.dialog.title')}</DialogTitle> <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 { LoadingSpinner } from '../../components/LoadingSpinner'
import { ErrorAlert } from '../../components/ErrorAlert' import { ErrorAlert } from '../../components/ErrorAlert'
import { EmptyState } from '../../components/EmptyState' import { EmptyState } from '../../components/EmptyState'
import { ConfirmDialog } from '../../components/ConfirmDialog'
import { DuplicateChainDialog } from '../../components/DuplicateChainDialog' import { DuplicateChainDialog } from '../../components/DuplicateChainDialog'
import { ClearSubmissionsDialog } from '../../components/ClearSubmissionsDialog' import { ClearSubmissionsDialog } from '../../components/ClearSubmissionsDialog'
import type { ChallengeChain } from '../../types/challenge' import type { ChallengeChain } from '../../types/challenge'
@@ -27,26 +26,28 @@ export const ChainsListPage: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const { t } = useTranslation() const { t } = useTranslation()
const { data: chains, isLoading, error, refetch } = useGetChainsQuery() const { data: chains, isLoading, error, refetch } = useGetChainsQuery()
const [deleteChain, { isLoading: isDeleting }] = useDeleteChainMutation() const [deleteChain] = useDeleteChainMutation()
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [chainToDelete, setChainToDelete] = useState<ChallengeChain | null>(null)
const [chainToDuplicate, setChainToDuplicate] = useState<ChallengeChain | null>(null) const [chainToDuplicate, setChainToDuplicate] = useState<ChallengeChain | null>(null)
const [chainToClearSubmissions, setChainToClearSubmissions] = useState<ChallengeChain | null>(null) const [chainToClearSubmissions, setChainToClearSubmissions] = useState<ChallengeChain | null>(null)
const [updatingChainId, setUpdatingChainId] = useState<string | null>(null) const [updatingChainId, setUpdatingChainId] = useState<string | null>(null)
const [updateChain] = useUpdateChainMutation() const [updateChain] = useUpdateChainMutation()
const handleDeleteChain = async () => { const handleDeleteChain = async (chain: ChallengeChain) => {
if (!chainToDelete) return const confirmed = window.confirm(
t('challenge.admin.chains.delete.confirm.message', { name: chain.name })
)
if (!confirmed) return
try { try {
await deleteChain(chainToDelete.id).unwrap() await deleteChain(chain.id).unwrap()
toaster.create({ toaster.create({
title: t('challenge.admin.common.success'), title: t('challenge.admin.common.success'),
description: t('challenge.admin.chains.deleted'), description: t('challenge.admin.chains.deleted'),
type: 'success', type: 'success',
}) })
setChainToDelete(null)
} catch (err) { } catch (err) {
toaster.create({ toaster.create({
title: t('challenge.admin.common.error'), title: t('challenge.admin.common.error'),
@@ -205,7 +206,7 @@ export const ChainsListPage: React.FC = () => {
size="sm" size="sm"
variant="ghost" variant="ghost"
colorPalette="red" colorPalette="red"
onClick={() => setChainToDelete(chain)} onClick={() => handleDeleteChain(chain)}
> >
{t('challenge.admin.chains.list.button.delete')} {t('challenge.admin.chains.list.button.delete')}
</Button> </Button>
@@ -218,16 +219,6 @@ export const ChainsListPage: React.FC = () => {
</Box> </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 <DuplicateChainDialog
isOpen={!!chainToDuplicate} isOpen={!!chainToDuplicate}
onClose={() => setChainToDuplicate(null)} onClose={() => setChainToDuplicate(null)}

View File

@@ -17,7 +17,6 @@ import { URLs } from '../../__data__/urls'
import { LoadingSpinner } from '../../components/LoadingSpinner' import { LoadingSpinner } from '../../components/LoadingSpinner'
import { ErrorAlert } from '../../components/ErrorAlert' import { ErrorAlert } from '../../components/ErrorAlert'
import { EmptyState } from '../../components/EmptyState' import { EmptyState } from '../../components/EmptyState'
import { ConfirmDialog } from '../../components/ConfirmDialog'
import type { ChallengeTask } from '../../types/challenge' import type { ChallengeTask } from '../../types/challenge'
import { toaster } from '../../components/ui/toaster' import { toaster } from '../../components/ui/toaster'
@@ -25,22 +24,24 @@ export const TasksListPage: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const { t } = useTranslation() const { t } = useTranslation()
const { data: tasks, isLoading, error, refetch } = useGetTasksQuery() const { data: tasks, isLoading, error, refetch } = useGetTasksQuery()
const [deleteTask, { isLoading: isDeleting }] = useDeleteTaskMutation() const [deleteTask] = useDeleteTaskMutation()
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [taskToDelete, setTaskToDelete] = useState<ChallengeTask | null>(null)
const handleDeleteTask = async () => { const handleDeleteTask = async (task: ChallengeTask) => {
if (!taskToDelete) return const confirmed = window.confirm(
t('challenge.admin.tasks.delete.confirm.message', { title: task.title })
)
if (!confirmed) return
try { try {
await deleteTask(taskToDelete.id).unwrap() await deleteTask(task.id).unwrap()
toaster.create({ toaster.create({
title: t('challenge.admin.common.success'), title: t('challenge.admin.common.success'),
description: t('challenge.admin.tasks.deleted'), description: t('challenge.admin.tasks.deleted'),
type: 'success', type: 'success',
}) })
setTaskToDelete(null)
} catch (_err) { } catch (_err) {
toaster.create({ toaster.create({
title: t('challenge.admin.common.error'), title: t('challenge.admin.common.error'),
@@ -152,7 +153,7 @@ export const TasksListPage: React.FC = () => {
size="sm" size="sm"
variant="ghost" variant="ghost"
colorPalette="red" colorPalette="red"
onClick={() => setTaskToDelete(task)} onClick={() => handleDeleteTask(task)}
> >
{t('challenge.admin.tasks.list.button.delete')} {t('challenge.admin.tasks.list.button.delete')}
</Button> </Button>
@@ -164,16 +165,6 @@ export const TasksListPage: React.FC = () => {
</Table.Root> </Table.Root>
</Box> </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> </Box>
) )
} }