From 173954f685c7a6dc1ba38a91a1810c1ecc29f422 Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Date: Wed, 10 Dec 2025 12:02:11 +0300 Subject: [PATCH] Add isActive field to challenge chains and update localization; implement functionality to toggle chain status in the UI, enhancing task management and user experience. --- locales/en.json | 5 +++ locales/ru.json | 5 +++ src/__data__/api/api.ts | 2 +- src/pages/chains/ChainFormPage.tsx | 23 ++++++++++++++ src/pages/chains/ChainsListPage.tsx | 48 ++++++++++++++++++++++++++++- src/types/challenge.ts | 3 ++ stubs/api/data/chains.json | 3 ++ stubs/api/index.js | 30 +++++++++++++----- 8 files changed, 109 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6c7ec38..a3a6b1a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -74,6 +74,8 @@ "challenge.admin.chains.button.add": "+ Add", "challenge.admin.chains.button.save": "Save changes", "challenge.admin.chains.button.create": "Create chain", + "challenge.admin.chains.field.isActive": "Active for students", + "challenge.admin.chains.field.isActive.helper": "If disabled, the chain will not appear in the user-facing list.", "challenge.admin.chains.list.title": "Task Chains", "challenge.admin.chains.list.create.button": "+ Create Chain", "challenge.admin.chains.list.search.placeholder": "Search by name...", @@ -84,8 +86,11 @@ "challenge.admin.chains.list.table.name": "Name", "challenge.admin.chains.list.table.tasks.count": "Number of tasks", "challenge.admin.chains.list.table.created": "Created date", + "challenge.admin.chains.list.table.status": "Status", "challenge.admin.chains.list.table.actions": "Actions", "challenge.admin.chains.list.badge.tasks": "tasks", + "challenge.admin.chains.list.status.active": "Active", + "challenge.admin.chains.list.status.inactive": "Inactive", "challenge.admin.chains.list.button.edit": "Edit", "challenge.admin.chains.list.button.delete": "Delete", "challenge.admin.chains.deleted": "Chain deleted", diff --git a/locales/ru.json b/locales/ru.json index d223a17..16a51cd 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -73,6 +73,8 @@ "challenge.admin.chains.button.add": "+ Добавить", "challenge.admin.chains.button.save": "Сохранить изменения", "challenge.admin.chains.button.create": "Создать цепочку", + "challenge.admin.chains.field.isActive": "Активна для студентов", + "challenge.admin.chains.field.isActive.helper": "Если выключить, цепочка не будет отображаться в пользовательском списке.", "challenge.admin.chains.list.title": "Цепочки заданий", "challenge.admin.chains.list.create.button": "+ Создать цепочку", "challenge.admin.chains.list.search.placeholder": "Поиск по названию...", @@ -83,8 +85,11 @@ "challenge.admin.chains.list.table.name": "Название", "challenge.admin.chains.list.table.tasks.count": "Количество заданий", "challenge.admin.chains.list.table.created": "Дата создания", + "challenge.admin.chains.list.table.status": "Статус", "challenge.admin.chains.list.table.actions": "Действия", "challenge.admin.chains.list.badge.tasks": "заданий", + "challenge.admin.chains.list.status.active": "Включена", + "challenge.admin.chains.list.status.inactive": "Выключена", "challenge.admin.chains.list.button.edit": "Редактировать", "challenge.admin.chains.list.button.delete": "Удалить", "challenge.admin.chains.deleted": "Цепочка удалена", diff --git a/src/__data__/api/api.ts b/src/__data__/api/api.ts index 688d4d1..5c65945 100644 --- a/src/__data__/api/api.ts +++ b/src/__data__/api/api.ts @@ -77,7 +77,7 @@ export const api = createApi({ // Chains getChains: builder.query({ - query: () => '/challenge/chains', + query: () => '/challenge/chains/admin', transformResponse: (response: { body: ChallengeChain[] }) => response.body, providesTags: ['Chain'], }), diff --git a/src/pages/chains/ChainFormPage.tsx b/src/pages/chains/ChainFormPage.tsx index 1c94da7..e16594e 100644 --- a/src/pages/chains/ChainFormPage.tsx +++ b/src/pages/chains/ChainFormPage.tsx @@ -42,11 +42,13 @@ export const ChainFormPage: React.FC = () => { const [name, setName] = useState('') const [selectedTasks, setSelectedTasks] = useState([]) 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 = () => { /> + {/* Active flag */} + + + + setIsActive(e.target.checked)} + disabled={isLoading} + style={{ cursor: isLoading ? 'not-allowed' : 'pointer' }} + /> + {t('challenge.admin.chains.field.isActive')} + + + {t('challenge.admin.chains.field.isActive.helper')} + + + + {/* Selected Tasks */} diff --git a/src/pages/chains/ChainsListPage.tsx b/src/pages/chains/ChainsListPage.tsx index fd6ee41..93824aa 100644 --- a/src/pages/chains/ChainsListPage.tsx +++ b/src/pages/chains/ChainsListPage.tsx @@ -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(null) + const [updatingChainId, setUpdatingChainId] = useState(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 } @@ -110,6 +136,7 @@ export const ChainsListPage: React.FC = () => { {t('challenge.admin.chains.list.table.name')} {t('challenge.admin.chains.list.table.tasks.count')} {t('challenge.admin.chains.list.table.created')} + {t('challenge.admin.chains.list.table.status')} {t('challenge.admin.chains.list.table.actions')} @@ -127,6 +154,25 @@ export const ChainsListPage: React.FC = () => { {formatDate(chain.createdAt)} + + + + {chain.isActive + ? t('challenge.admin.chains.list.status.active') + : t('challenge.admin.chains.list.status.inactive')} + + + +