contests
This commit is contained in:
191
src/views/home/contests/ModalCreate.tsx
Normal file
191
src/views/home/contests/ModalCreate.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Modal } from '../../../components/modal/Modal';
|
||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||||
import { SecondaryButton } from '../../../components/button/SecondaryButton';
|
||||
import { Input } from '../../../components/input/Input';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { createContest } from '../../../redux/slices/contests';
|
||||
import { CreateContestBody } from '../../../redux/slices/contests';
|
||||
import DateRangeInput from '../../../components/input/DateRangeInput';
|
||||
|
||||
interface ModalCreateContestProps {
|
||||
active: boolean;
|
||||
setActive: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
active,
|
||||
setActive,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const status = useAppSelector((state) => state.contests.statuses.create);
|
||||
|
||||
const [form, setForm] = useState<CreateContestBody>({
|
||||
name: '',
|
||||
description: '',
|
||||
scheduleType: 'AlwaysOpen',
|
||||
visibility: 'Public',
|
||||
startsAt: null,
|
||||
endsAt: null,
|
||||
attemptDurationMinutes: null,
|
||||
maxAttempts: null,
|
||||
allowEarlyFinish: false,
|
||||
groupId: null,
|
||||
missionIds: null,
|
||||
articleIds: null,
|
||||
participantIds: null,
|
||||
organizerIds: null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'successful') {
|
||||
setActive(false);
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
const handleChange = (key: keyof CreateContestBody, value: any) => {
|
||||
setForm((prev) => ({ ...prev, [key]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
dispatch(createContest(form));
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white"
|
||||
onOpenChange={setActive}
|
||||
open={active}
|
||||
backdrop="blur"
|
||||
>
|
||||
<div className="w-[550px]">
|
||||
<div className="font-bold text-[30px] mb-[10px]">
|
||||
Создать контест
|
||||
</div>
|
||||
|
||||
<Input
|
||||
name="name"
|
||||
type="text"
|
||||
label="Название"
|
||||
className="mt-[10px]"
|
||||
placeholder="Введите название"
|
||||
onChange={(v) => handleChange('name', v)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
name="description"
|
||||
type="text"
|
||||
label="Описание"
|
||||
className="mt-[10px]"
|
||||
placeholder="Введите описание"
|
||||
onChange={(v) => handleChange('description', v)}
|
||||
/>
|
||||
|
||||
<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={form.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={form.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={form.startsAt || ''}
|
||||
endValue={form.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={!!form.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]">
|
||||
<PrimaryButton
|
||||
onClick={handleSubmit}
|
||||
text="Создать"
|
||||
disabled={status === 'loading'}
|
||||
/>
|
||||
<SecondaryButton
|
||||
onClick={() => setActive(false)}
|
||||
text="Отмена"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalCreateContest;
|
||||
Reference in New Issue
Block a user