formate
This commit is contained in:
@@ -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;
|
|
||||||
|
|||||||
@@ -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: '',
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user