contest create modal
This commit is contained in:
38
src/components/input/DateInput.tsx
Normal file
38
src/components/input/DateInput.tsx
Normal 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;
|
||||
@@ -12,7 +12,9 @@ import { CreateContestBody } from '../../../redux/slices/contests';
|
||||
import DateRangeInput from '../../../components/input/DateRangeInput';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
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 {
|
||||
if (!localDateTime) return '';
|
||||
@@ -39,21 +41,36 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
(state) => state.contests.createContest.status,
|
||||
);
|
||||
|
||||
const scheduleTypeItems: DropDownListItem[] = [
|
||||
{value: "", text: ""},
|
||||
{value: "", text: ""},
|
||||
{value: "", text: ""},
|
||||
const visibilityItems: DropDownListItem[] = [
|
||||
{ value: "Public", text: "Публичный"},
|
||||
{ value: "GroupPrivate", 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>({
|
||||
name: '',
|
||||
description: '',
|
||||
scheduleType: 'AlwaysOpen',
|
||||
visibility: 'Public',
|
||||
startsAt: '',
|
||||
endsAt: '',
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
startsAt: toLocal(now),
|
||||
endsAt: toLocal(plus60),
|
||||
attemptDurationMinutes: 60,
|
||||
maxAttempts: 1,
|
||||
allowEarlyFinish: false,
|
||||
missionIds: [],
|
||||
articleIds: [],
|
||||
@@ -88,6 +105,7 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
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>
|
||||
|
||||
<label className="block text-sm mb-1">
|
||||
Тип расписания
|
||||
Тип контеста
|
||||
</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"
|
||||
value={form.scheduleType}
|
||||
onChange={(e) =>
|
||||
@@ -144,12 +173,19 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
<option value="RollingWindow">
|
||||
Скользящее окно
|
||||
</option>
|
||||
</select>
|
||||
</select> */}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<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"
|
||||
value={form.visibility}
|
||||
onChange={(e) =>
|
||||
@@ -162,38 +198,85 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
>
|
||||
<option value="Public">Публичный</option>
|
||||
<option value="GroupPrivate">Групповой</option>
|
||||
</select>
|
||||
</select> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Даты начала и конца */}
|
||||
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
|
||||
{/* <div className="grid grid-cols-2 gap-[10px] mt-[10px]">
|
||||
<DateRangeInput
|
||||
startValue={form.startsAt || ''}
|
||||
endValue={form.endsAt || ''}
|
||||
onChange={handleChange}
|
||||
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]">
|
||||
<NumberInput
|
||||
defaultState={form.attemptDurationMinutes}
|
||||
name="attemptDurationMinutes"
|
||||
label="Длительность попытки (мин)"
|
||||
placeholder="Например: 60"
|
||||
minValue={1}
|
||||
maxValue={365 * 24 * 60}
|
||||
onChange={(v) =>
|
||||
handleChange('attemptDurationMinutes', Number(v))
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
defaultState={form.maxAttempts}
|
||||
name="maxAttempts"
|
||||
label="Макс. попыток"
|
||||
placeholder="Например: 3"
|
||||
minValue={1}
|
||||
maxValue={100}
|
||||
onChange={(v) => handleChange('maxAttempts', Number(v))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Разрешить раннее завершение */}
|
||||
{/* <div className="flex items-center gap-[10px] mt-[15px]">
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user