Add isActive field to challenge chains and update localization; implement functionality to toggle chain status in the UI, enhancing task management and user experience.

This commit is contained in:
2025-12-10 12:02:11 +03:00
parent 4e1b290f99
commit 173954f685
8 changed files with 109 additions and 10 deletions

View File

@@ -77,7 +77,7 @@ export const api = createApi({
// Chains
getChains: builder.query<ChallengeChain[], void>({
query: () => '/challenge/chains',
query: () => '/challenge/chains/admin',
transformResponse: (response: { body: ChallengeChain[] }) => response.body,
providesTags: ['Chain'],
}),

View File

@@ -42,11 +42,13 @@ export const ChainFormPage: React.FC = () => {
const [name, setName] = useState('')
const [selectedTasks, setSelectedTasks] = useState<ChallengeTask[]>([])
const [searchQuery, setSearchQuery] = useState('')
const [isActive, setIsActive] = useState(true)
useEffect(() => {
if (chain) {
setName(chain.name)
setSelectedTasks(chain.tasks)
setIsActive(chain.isActive !== false)
}
}, [chain])
@@ -80,6 +82,7 @@ export const ChainFormPage: React.FC = () => {
data: {
name: name.trim(),
taskIds: taskIds,
isActive,
},
}).unwrap()
toaster.create({
@@ -91,6 +94,7 @@ export const ChainFormPage: React.FC = () => {
await createChain({
name: name.trim(),
taskIds: taskIds,
isActive,
}).unwrap()
toaster.create({
title: t('challenge.admin.common.success'),
@@ -191,6 +195,25 @@ export const ChainFormPage: React.FC = () => {
/>
</Field.Root>
{/* Active flag */}
<Field.Root>
<HStack justify="space-between" align="flex-start">
<HStack gap={2}>
<input
type="checkbox"
checked={isActive}
onChange={(e) => setIsActive(e.target.checked)}
disabled={isLoading}
style={{ cursor: isLoading ? 'not-allowed' : 'pointer' }}
/>
<Text>{t('challenge.admin.chains.field.isActive')}</Text>
</HStack>
<Text fontSize="sm" color="gray.500" maxW="md">
{t('challenge.admin.chains.field.isActive.helper')}
</Text>
</HStack>
</Field.Root>
{/* Selected Tasks */}
<Box>
<Text fontWeight="bold" mb={3}>

View File

@@ -12,7 +12,7 @@ import {
Text,
Badge,
} from '@chakra-ui/react'
import { useGetChainsQuery, useDeleteChainMutation } from '../../__data__/api/api'
import { useGetChainsQuery, useDeleteChainMutation, useUpdateChainMutation } from '../../__data__/api/api'
import { URLs } from '../../__data__/urls'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { ErrorAlert } from '../../components/ErrorAlert'
@@ -29,6 +29,8 @@ export const ChainsListPage: React.FC = () => {
const [searchQuery, setSearchQuery] = useState('')
const [chainToDelete, setChainToDelete] = useState<ChallengeChain | null>(null)
const [updatingChainId, setUpdatingChainId] = useState<string | null>(null)
const [updateChain] = useUpdateChainMutation()
const handleDeleteChain = async () => {
if (!chainToDelete) return
@@ -50,6 +52,30 @@ export const ChainsListPage: React.FC = () => {
}
}
const handleToggleActive = async (chain: ChallengeChain, nextValue: boolean) => {
setUpdatingChainId(chain.id)
try {
await updateChain({
id: chain.id,
data: { isActive: nextValue },
}).unwrap()
toaster.create({
title: t('challenge.admin.common.success'),
description: t('challenge.admin.chains.updated'),
type: 'success',
})
} catch {
toaster.create({
title: t('challenge.admin.common.error'),
description: t('challenge.admin.chains.save.error'),
type: 'error',
})
} finally {
setUpdatingChainId(null)
}
}
if (isLoading) {
return <LoadingSpinner message={t('challenge.admin.chains.list.loading')} />
}
@@ -110,6 +136,7 @@ export const ChainsListPage: React.FC = () => {
<Table.ColumnHeader>{t('challenge.admin.chains.list.table.name')}</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.chains.list.table.tasks.count')}</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.chains.list.table.created')}</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.chains.list.table.status')}</Table.ColumnHeader>
<Table.ColumnHeader textAlign="right">{t('challenge.admin.chains.list.table.actions')}</Table.ColumnHeader>
</Table.Row>
</Table.Header>
@@ -127,6 +154,25 @@ export const ChainsListPage: React.FC = () => {
{formatDate(chain.createdAt)}
</Text>
</Table.Cell>
<Table.Cell>
<HStack gap={3} justify="flex-start">
<Badge colorPalette={chain.isActive ? 'green' : 'gray'} variant="subtle">
{chain.isActive
? t('challenge.admin.chains.list.status.active')
: t('challenge.admin.chains.list.status.inactive')}
</Badge>
<Button
size="xs"
variant="outline"
onClick={() => handleToggleActive(chain, !chain.isActive)}
isDisabled={updatingChainId === chain.id}
>
{chain.isActive
? t('challenge.admin.chains.list.status.inactive')
: t('challenge.admin.chains.list.status.active')}
</Button>
</HStack>
</Table.Cell>
<Table.Cell textAlign="right">
<HStack gap={2} justify="flex-end">
<Button

View File

@@ -27,6 +27,7 @@ export interface ChallengeChain {
id: string
name: string
tasks: ChallengeTask[] // Populated
isActive: boolean
createdAt: string
updatedAt: string
}
@@ -132,11 +133,13 @@ export interface UpdateTaskRequest {
export interface CreateChainRequest {
name: string
taskIds: string[] // Array of task IDs
isActive?: boolean
}
export interface UpdateChainRequest {
name?: string
taskIds?: string[]
isActive?: boolean
}
// ========== Stats v2 Types ==========