This commit is contained in:
Виталий Лавшонок
2025-12-09 16:24:40 +03:00
parent 46e27616b2
commit 4391114dc3
5 changed files with 114 additions and 127 deletions

View File

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

View File

@@ -43,17 +43,15 @@ const ContestEditor = () => {
const [missionIdInput, setMissionIdInput] = useState<string>(''); const [missionIdInput, setMissionIdInput] = useState<string>('');
const now = new Date(); const now = new Date();
const plus60 = new Date(now.getTime() + 60 * 60 * 1000); 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 toLocal = (d: Date) => {
const off = d.getTimezoneOffset();
const local = new Date(d.getTime() - off * 60000);
return local.toISOString().slice(0, 16);
};
const [contest, setContest] = useState<CreateContestBody>({ const [contest, setContest] = useState<CreateContestBody>({
name: '', name: '',
description: '', description: '',

View File

@@ -2,7 +2,10 @@ import { useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { setMenuActivePage } from '../../../redux/slices/store'; import { setMenuActivePage } from '../../../redux/slices/store';
import { Navigate, Route, Routes, useParams } from 'react-router-dom'; import { Navigate, Route, Routes, useParams } from 'react-router-dom';
import { fetchContestById, fetchMyAttemptsInContest } from '../../../redux/slices/contests'; import {
fetchContestById,
fetchMyAttemptsInContest,
} from '../../../redux/slices/contests';
import ContestMissions from './Missions'; import ContestMissions from './Missions';
import Submissions from './Submissions'; import Submissions from './Submissions';

View File

@@ -1,7 +1,4 @@
import { import { FilterDropDown, FilterItem } from '../../../components/filters/Filter';
FilterDropDown,
FilterItem,
} from '../../../components/filters/Filter';
import { SorterDropDown } from '../../../components/filters/Sorter'; import { SorterDropDown } from '../../../components/filters/Sorter';
import { SearchInput } from '../../../components/input/SearchInput'; import { SearchInput } from '../../../components/input/SearchInput';

View File

@@ -9,10 +9,12 @@ import {
setContestStatus, setContestStatus,
} from '../../../redux/slices/contests'; } from '../../../redux/slices/contests';
import { CreateContestBody } from '../../../redux/slices/contests'; import { CreateContestBody } from '../../../redux/slices/contests';
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 { DropDownList, DropDownListItem } from '../../../components/filters/DropDownList'; import {
DropDownList,
DropDownListItem,
} from '../../../components/filters/DropDownList';
import DateInput from '../../../components/input/DateInput'; import DateInput from '../../../components/input/DateInput';
import { cn } from '../../../lib/cn'; import { cn } from '../../../lib/cn';
@@ -42,25 +44,24 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
); );
const visibilityItems: DropDownListItem[] = [ const visibilityItems: DropDownListItem[] = [
{ value: "Public", text: "Публичный"}, { value: 'Public', text: 'Публичный' },
{ value: "GroupPrivate", text: "Для группы"}, { value: 'GroupPrivate', text: 'Для группы' },
] ];
const scheduleTypeItems: DropDownListItem[] = [ const scheduleTypeItems: DropDownListItem[] = [
{value: "AlwaysOpen", text: "Всегда открыт"}, { value: 'AlwaysOpen', text: 'Всегда открыт' },
{value: "FixedWindow", text: "Фиксированое окно"}, { value: 'FixedWindow', text: 'Фиксированое окно' },
{value: "RollingWindow", text: "Скользящее окно"}, { value: 'RollingWindow', text: 'Скользящее окно' },
] ];
const now = new Date(); const now = new Date();
const plus60 = new Date(now.getTime() + 60 * 60 * 1000); const plus60 = new Date(now.getTime() + 60 * 60 * 1000);
const toLocal = (d: Date) => {
const toLocal = (d: Date) => { const off = d.getTimezoneOffset();
const off = d.getTimezoneOffset(); const local = new Date(d.getTime() - off * 60000);
const local = new Date(d.getTime() - off * 60000); return local.toISOString().slice(0, 16);
return local.toISOString().slice(0, 16); };
};
const [form, setForm] = useState<CreateContestBody>({ const [form, setForm] = useState<CreateContestBody>({
name: '', name: '',
@@ -105,7 +106,6 @@ return local.toISOString().slice(0, 16);
); );
}; };
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"
@@ -136,24 +136,19 @@ return local.toISOString().slice(0, 16);
onChange={(v) => handleChange('description', v)} onChange={(v) => handleChange('description', v)}
/> />
<div className="grid grid-cols-2 gap-[10px] mt-[10px]"> <div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<div> <div>
<label className="block text-sm mb-1"> <label className="block text-sm mb-1">
Тип контеста Тип контеста
</label> </label>
<DropDownList <DropDownList
items={scheduleTypeItems} items={scheduleTypeItems}
onChange={(v) => { onChange={(v) => {
handleChange("scheduleType", v); handleChange('scheduleType', v);
}} }}
weight='w-full' weight="w-full"
/> />
{/* <select {/* <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"
@@ -178,13 +173,13 @@ return local.toISOString().slice(0, 16);
<div> <div>
<label className="block text-sm mb-1">Видимость</label> <label className="block text-sm mb-1">Видимость</label>
<DropDownList <DropDownList
items={visibilityItems} items={visibilityItems}
onChange={(v) => { onChange={(v) => {
handleChange("visibility", v); handleChange('visibility', v);
}} }}
weight='w-full' weight="w-full"
/> />
{/* <select {/* <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}
@@ -212,60 +207,59 @@ return local.toISOString().slice(0, 16);
/> />
</div> */} </div> */}
<div <div
className={cn( className={cn(
' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-200', ' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-200',
form.visibility == "GroupPrivate" && 'grid-rows-[1fr] opacity-100', form.visibility == 'GroupPrivate' &&
)} 'grid-rows-[1fr] opacity-100',
> )}
>
<div className="overflow-hidden"> <div className="overflow-hidden">
<div className='grid grid-cols-2 gap-[10px] mt-[10px]'> <div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<NumberInput <NumberInput
defaultState={form.groupId??1} defaultState={form.groupId ?? 1}
name="groupId" name="groupId"
label="Id группы" label="Id группы"
placeholder="Например: 3" placeholder="Например: 3"
minValue={1} minValue={1}
maxValue={1000000000000000} maxValue={1000000000000000}
onChange={(v) => handleChange('groupId', Number(v))} onChange={(v) =>
/> handleChange('groupId', Number(v))
}
</div></div> />
</div>
</div> </div>
</div>
{/* Даты */} {/* Даты */}
<div <div
className={cn( className={cn(
' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-200', ' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-200',
form.scheduleType != "AlwaysOpen" && 'grid-rows-[1fr] opacity-100', form.scheduleType != 'AlwaysOpen' &&
)} 'grid-rows-[1fr] opacity-100',
> )}
>
<div className="overflow-hidden"> <div className="overflow-hidden">
<div className="grid grid-cols-2 gap-[10px] mt-[10px]"> <div className="grid grid-cols-2 gap-[10px] mt-[10px]">
<DateInput <DateInput
label="Дата начала" label="Дата начала"
value={form.startsAt} value={form.startsAt}
onChange={(v) => handleChange('startsAt', v)} onChange={(v) => handleChange('startsAt', v)}
/> />
<DateInput
label="Дата окончания"
value={form.endsAt}
onChange={(v) => handleChange('endsAt', v)}
/>
</div>
</div>
<DateInput
label="Дата окончания"
value={form.endsAt}
onChange={(v) => handleChange('endsAt', v)}
/>
</div>
</div> </div>
</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} defaultState={form.attemptDurationMinutes}
name="attemptDurationMinutes" name="attemptDurationMinutes"
label="Длительность попытки (мин)" label="Длительность попытки (мин)"
placeholder="Например: 60" placeholder="Например: 60"
@@ -276,7 +270,7 @@ onChange={(v) => handleChange('endsAt', v)}
} }
/> />
<NumberInput <NumberInput
defaultState={form.maxAttempts} defaultState={form.maxAttempts}
name="maxAttempts" name="maxAttempts"
label="Макс. попыток" label="Макс. попыток"
placeholder="Например: 3" placeholder="Например: 3"
@@ -286,8 +280,6 @@ onChange={(v) => handleChange('endsAt', v)}
/> />
</div> </div>
{/* Разрешить раннее завершение */} {/* Разрешить раннее завершение */}
{/* <div className="flex items-center gap-[10px] mt-[15px]"> {/* <div className="flex items-center gap-[10px] mt-[15px]">
<input <input