dont work

This commit is contained in:
Виталий Лавшонок
2025-11-06 15:09:10 +03:00
parent dc6df1480e
commit 1b39b8c77f
6 changed files with 546 additions and 42 deletions

336
src/pages/ContestEditor.tsx Normal file
View File

@@ -0,0 +1,336 @@
import { useEffect, useState } from 'react';
import Header from '../views/articleeditor/Header';
import { PrimaryButton } from '../components/button/PrimaryButton';
import { Input } from '../components/input/Input';
import { useAppDispatch, useAppSelector } from '../redux/hooks';
import {
createContest,
CreateContestBody,
fetchContestById,
} from '../redux/slices/contests';
import DateRangeInput from '../components/input/DateRangeInput';
import { useQuery } from '../hooks/useQuery';
import { useNavigate } from 'react-router-dom';
import { fetchMissionById, Mission } from '../redux/slices/missions';
import { ReverseButton } from '../components/button/ReverseButton';
/**
* Страница создания / редактирования контеста
*/
const ContestEditor = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const status = useAppSelector(
(state) => state.contests.createContest.status,
);
const [missionIdInput, setMissionIdInput] = useState<string>('');
const query = useQuery();
const back = query.get('back') ?? undefined;
const contestId = Number(query.get('contestId') ?? undefined);
const refactor = !!contestId;
const [contest, setContest] = useState<CreateContestBody>({
name: '',
description: '',
scheduleType: 'AlwaysOpen',
visibility: 'Public',
startsAt: null,
endsAt: null,
attemptDurationMinutes: null,
maxAttempts: null,
allowEarlyFinish: false,
groupId: null,
missionIds: [],
articleIds: [],
});
const [missions, setMissions] = useState<Mission[]>([]);
const { contest: contestById, status: contestByIdstatus } = useAppSelector(
(state) => state.contests.fetchContestById,
);
console.log(contestByIdstatus, contestById);
useEffect(() => {
if (status === 'successful') {
}
}, [status]);
const handleChange = (key: keyof CreateContestBody, value: any) => {
setContest((prev) => ({ ...prev, [key]: value }));
};
const handleSubmit = () => {
dispatch(createContest(contest));
};
const addMission = () => {
const id = Number(missionIdInput.trim());
if (!id || contest.missionIds?.includes(id)) return;
dispatch(fetchMissionById(id))
.unwrap()
.then((mission) => {
setMissions((prev) => [...prev, mission]);
setContest((prev) => ({
...prev,
missionIds: [...(prev.missionIds ?? []), id],
}));
setMissionIdInput('');
})
.catch((err) => {
console.error('Ошибка при загрузке миссии:', err);
});
};
const removeMission = (removeId: number) => {
setContest({
...contest,
missionIds: contest.missionIds?.filter((v) => v !== removeId),
});
setMissions(missions.filter((v) => v.id != removeId));
};
useEffect(() => {
if (refactor) {
dispatch(fetchContestById(contestId));
}
}, [refactor]);
useEffect(() => {
if (refactor && contestByIdstatus == 'successful' && contestById) {
setContest({
...contestById,
missionIds: [],
visibility: 'Public',
scheduleType: 'AlwaysOpen',
});
}
}, [contestById]);
return (
<div className="h-screen grid grid-rows-[60px,1fr] text-liquid-white">
<Header backClick={() => navigate(back || '/home/contests')} />
<div className="grid grid-cols-2 h-full min-h-0">
{/* Левая панешь */}
<div className="overflow-y-auto min-h-0 overflow-hidden">
<div className="p-4 border-r border-gray-700 flex flex-col h-full">
<h2 className="text-lg font-semibold mb-3 text-gray-100"></h2>
<div className="">
<div className="font-bold text-[30px] mb-[10px]">
{refactor
? `Редактирвоание контеста #${contestId} \"${contestById?.name}\"`
: 'Создать контест'}
</div>
<Input
name="name"
type="text"
label="Название"
className="mt-[10px]"
placeholder="Введите название"
onChange={(v) => handleChange('name', v)}
defaultState={contest.name ?? ''}
/>
<Input
name="description"
type="text"
label="Описание"
className="mt-[10px]"
placeholder="Введите описание"
onChange={(v) => handleChange('description', v)}
defaultState={contest.description ?? ''}
/>
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<div>
<label className="block text-sm mb-1">
Тип расписания
</label>
<select
className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter"
value={contest.scheduleType}
onChange={(e) =>
handleChange(
'scheduleType',
e.target
.value as CreateContestBody['scheduleType'],
)
}
>
<option value="AlwaysOpen">
Всегда открыт
</option>
<option value="FixedWindow">
Фиксированные даты
</option>
<option value="RollingWindow">
Скользящее окно
</option>
</select>
</div>
<div>
<label className="block text-sm mb-1">
Видимость
</label>
<select
className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter"
value={contest.visibility}
onChange={(e) =>
handleChange(
'visibility',
e.target
.value as CreateContestBody['visibility'],
)
}
>
<option value="Public">
Публичный
</option>
<option value="GroupPrivate">
Групповой
</option>
</select>
</div>
</div>
{/* Даты начала и конца */}
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<DateRangeInput
startValue={contest.startsAt || ''}
endValue={contest.endsAt || ''}
onChange={handleChange}
className="mt-[10px]"
/>
</div>
{/* Продолжительность и лимиты */}
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<Input
name="attemptDurationMinutes"
type="number"
label="Длительность попытки (мин)"
placeholder="Например: 60"
onChange={(v) =>
handleChange(
'attemptDurationMinutes',
Number(v),
)
}
/>
<Input
name="maxAttempts"
type="number"
label="Макс. попыток"
placeholder="Например: 3"
onChange={(v) =>
handleChange('maxAttempts', Number(v))
}
/>
</div>
{/* Разрешить раннее завершение */}
<div className="flex items-center gap-[10px] mt-[15px]">
<input
id="allowEarlyFinish"
type="checkbox"
checked={!!contest.allowEarlyFinish}
onChange={(e) =>
handleChange(
'allowEarlyFinish',
e.target.checked,
)
}
/>
<label htmlFor="allowEarlyFinish">
Разрешить раннее завершение
</label>
</div>
{/* Кнопки */}
<div className="flex flex-row w-full items-center justify-end mt-[20px] gap-[20px]">
{refactor ? (
<>
<PrimaryButton
onClick={handleSubmit}
text="Сохранить"
disabled={status === 'loading'}
/>
<ReverseButton
color="error"
onClick={handleSubmit}
text="Удалить"
disabled={status === 'loading'}
/>
</>
) : (
<PrimaryButton
onClick={handleSubmit}
text="Создать"
disabled={status === 'loading'}
/>
)}
</div>
</div>
</div>
</div>
{/* Правая панель */}
<div className="overflow-y-auto min-h-0 overflow-hidden">
<div className="p-4 border-r border-gray-700 flex flex-col h-full">
<h2 className="text-lg font-semibold mb-3 text-gray-100"></h2>
{/* Блок для тегов */}
<div className="mt-[20px] max-w-[600px]">
<div className="grid grid-cols-[1fr,140px] items-end gap-2">
<Input
name="missionId"
autocomplete="missionId"
className="mt-[20px] max-w-[600px]"
type="number"
label="ID миссии"
onChange={(v) => {
setMissionIdInput(v);
}}
defaultState={missionIdInput}
placeholder="458"
onKeyDown={(e) => {
if (e.key == 'Enter') addMission();
}}
/>
<PrimaryButton
onClick={addMission}
text="Добавить"
className="h-[40px] w-[140px]"
/>
</div>
<div className="flex flex-wrap gap-[10px] mt-2">
{missions.map((v, i) => (
<div
key={i}
className="flex items-center gap-1 bg-liquid-lighter px-3 py-1 rounded-full"
>
<span>{v.id}</span>
<span>{v.name}</span>
<button
onClick={() => removeMission(v.id)}
className="text-liquid-red font-bold ml-[5px]"
>
×
</button>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ContestEditor;