contest create modal

This commit is contained in:
Виталий Лавшонок
2025-12-09 11:44:53 +03:00
parent a4622cf8d2
commit 4fcbde6a06
2 changed files with 137 additions and 16 deletions

View File

@@ -0,0 +1,38 @@
// DateInput.tsx
import React from 'react';
interface DateInputProps {
label?: string;
value?: string;
defaultValue?: string;
onChange: (value: string) => void;
className?: string;
}
const DateInput: React.FC<DateInputProps> = ({
label = 'Дата',
value,
defaultValue,
onChange,
className = '',
}) => {
return (
<div className={`flex flex-col gap-1 ${className}`}>
<label className="block text-sm font-medium text-gray-700">
{label}
</label>
<input
type="datetime-local"
value={value}
defaultValue={defaultValue}
onChange={(e) => onChange(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
/>
</div>
);
};
export default DateInput;

View File

@@ -12,7 +12,9 @@ import { CreateContestBody } from '../../../redux/slices/contests';
import DateRangeInput from '../../../components/input/DateRangeInput'; import DateRangeInput from '../../../components/input/DateRangeInput';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { NumberInput } from '../../../components/input/NumberInput'; import { NumberInput } from '../../../components/input/NumberInput';
import { DropDownListItem } from '../../../components/filters/DropDownList'; import { DropDownList, DropDownListItem } from '../../../components/filters/DropDownList';
import DateInput from '../../../components/input/DateInput';
import { cn } from '../../../lib/cn';
function toUtc(localDateTime?: string): string { function toUtc(localDateTime?: string): string {
if (!localDateTime) return ''; if (!localDateTime) return '';
@@ -39,21 +41,36 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
(state) => state.contests.createContest.status, (state) => state.contests.createContest.status,
); );
const scheduleTypeItems: DropDownListItem[] = [ const visibilityItems: DropDownListItem[] = [
{value: "", text: ""}, { value: "Public", text: "Публичный"},
{value: "", text: ""}, { value: "GroupPrivate", text: "Для группы"},
{value: "", text: ""},
] ]
const scheduleTypeItems: DropDownListItem[] = [
{value: "AlwaysOpen", text: "Всегда открыт"},
{value: "FixedWindow", text: "Фиксированое окно"},
{value: "RollingWindow", text: "Скользящее окно"},
]
const now = new Date();
const plus60 = new Date(now.getTime() + 60 * 60 * 1000);
const toLocal = (d: Date) => {
const off = d.getTimezoneOffset();
const local = new Date(d.getTime() - off * 60000);
return local.toISOString().slice(0, 16);
};
const [form, setForm] = useState<CreateContestBody>({ const [form, setForm] = useState<CreateContestBody>({
name: '', name: '',
description: '', description: '',
scheduleType: 'AlwaysOpen', scheduleType: 'AlwaysOpen',
visibility: 'Public', visibility: 'Public',
startsAt: '', startsAt: toLocal(now),
endsAt: '', endsAt: toLocal(plus60),
attemptDurationMinutes: 0, attemptDurationMinutes: 60,
maxAttempts: 0, maxAttempts: 1,
allowEarlyFinish: false, allowEarlyFinish: false,
missionIds: [], missionIds: [],
articleIds: [], articleIds: [],
@@ -88,6 +105,7 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
); );
}; };
return ( return (
<Modal <Modal
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white" className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white"
@@ -124,9 +142,20 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
<div> <div>
<label className="block text-sm mb-1"> <label className="block text-sm mb-1">
Тип расписания Тип контеста
</label> </label>
<select
<DropDownList
items={scheduleTypeItems}
onChange={(v) => {
handleChange("scheduleType", v);
}}
weight='w-full'
/>
{/* <select
className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter" className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter"
value={form.scheduleType} value={form.scheduleType}
onChange={(e) => onChange={(e) =>
@@ -144,12 +173,19 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
<option value="RollingWindow"> <option value="RollingWindow">
Скользящее окно Скользящее окно
</option> </option>
</select> </select> */}
</div> </div>
<div> <div>
<label className="block text-sm mb-1">Видимость</label> <label className="block text-sm mb-1">Видимость</label>
<select <DropDownList
items={visibilityItems}
onChange={(v) => {
handleChange("visibility", v);
}}
weight='w-full'
/>
{/* <select
className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter" className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter"
value={form.visibility} value={form.visibility}
onChange={(e) => onChange={(e) =>
@@ -162,38 +198,85 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
> >
<option value="Public">Публичный</option> <option value="Public">Публичный</option>
<option value="GroupPrivate">Групповой</option> <option value="GroupPrivate">Групповой</option>
</select> </select> */}
</div> </div>
</div> </div>
{/* Даты начала и конца */} {/* Даты начала и конца */}
<div className="grid grid-cols-2 gap-[10px] mt-[10px]"> {/* <div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<DateRangeInput <DateRangeInput
startValue={form.startsAt || ''} startValue={form.startsAt || ''}
endValue={form.endsAt || ''} endValue={form.endsAt || ''}
onChange={handleChange} onChange={handleChange}
className="mt-[10px]" className="mt-[10px]"
/> />
</div> </div> */}
<div
className={cn(
' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-200',
form.visibility == "GroupPrivate" && 'grid-rows-[1fr] opacity-100',
)}
>
<div className="overflow-hidden">
<div className='grid grid-cols-2 gap-[10px] mt-[10px]'>
<NumberInput
defaultState={form.groupId??1}
name="groupId"
label="Id группы"
placeholder="Например: 3"
minValue={1}
maxValue={1000000000000000}
onChange={(v) => handleChange('groupId', Number(v))}
/>
</div></div>
</div>
{/* Даты */}
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<DateInput
label="Дата начала"
value={form.startsAt}
onChange={(v) => handleChange('startsAt', v)}
/>
<DateInput
label="Дата окончания"
value={form.endsAt}
onChange={(v) => handleChange('endsAt', v)}
/>
</div>
{/* Продолжительность и лимиты */} {/* Продолжительность и лимиты */}
<div className="grid grid-cols-2 gap-[10px] mt-[10px]"> <div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<NumberInput <NumberInput
defaultState={form.attemptDurationMinutes}
name="attemptDurationMinutes" name="attemptDurationMinutes"
label="Длительность попытки (мин)" label="Длительность попытки (мин)"
placeholder="Например: 60" placeholder="Например: 60"
minValue={1}
maxValue={365 * 24 * 60}
onChange={(v) => onChange={(v) =>
handleChange('attemptDurationMinutes', Number(v)) handleChange('attemptDurationMinutes', Number(v))
} }
/> />
<NumberInput <NumberInput
defaultState={form.maxAttempts}
name="maxAttempts" name="maxAttempts"
label="Макс. попыток" label="Макс. попыток"
placeholder="Например: 3" placeholder="Например: 3"
minValue={1}
maxValue={100}
onChange={(v) => handleChange('maxAttempts', Number(v))} onChange={(v) => handleChange('maxAttempts', Number(v))}
/> />
</div> </div>
{/* Разрешить раннее завершение */} {/* Разрешить раннее завершение */}
{/* <div className="flex items-center gap-[10px] mt-[15px]"> {/* <div className="flex items-center gap-[10px] mt-[15px]">
<input <input