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:
@@ -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> = ({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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> = ({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user