Merge remote-tracking branch 'origin/main' into feature/race-cover-images
Some checks failed
CI / build-and-test (pull_request) Has been cancelled

# Conflicts:
#	frontend/package-lock.json
#	frontend/package.json
This commit is contained in:
Vaka.pro
2026-04-27 23:01:19 +03:00
7 changed files with 258 additions and 48 deletions

View File

@@ -96,6 +96,17 @@ function validateForm(form: FormData): string[] {
return errors;
}
function isRaceDateTodayOrPast(date: string): boolean {
if (!date.trim()) {
return false;
}
const today = new Date();
const y = today.getFullYear();
const m = String(today.getMonth() + 1).padStart(2, "0");
const d = String(today.getDate()).padStart(2, "0");
return isRaceDateInPast(date) || date.slice(0, 10) === `${y}-${m}-${d}`;
}
export function RaceFormPage(): JSX.Element {
const { raceId } = useParams<{ raceId: string }>();
const navigate = useNavigate();
@@ -248,6 +259,7 @@ export function RaceFormPage(): JSX.Element {
);
const hideOrgScheduleFields = isEditMode && isRaceDateInPast(form.date);
const showResultFields = isRaceDateTodayOrPast(form.date);
const pageTitle = isEditMode ? "Редактирование старта" : "Новый старт";
if (isLoading) {
@@ -414,33 +426,35 @@ export function RaceFormPage(): JSX.Element {
</label>
</fieldset>
<fieldset className="race-form__group">
<legend className="race-form__legend">Результаты</legend>
{showResultFields ? (
<fieldset className="race-form__group">
<legend className="race-form__legend">Результаты</legend>
<label className="race-form__field">
<span className="race-form__label">Финишное время</span>
<input
className="race-form__input"
type="text"
name="finishTime"
value={form.finishTime}
onChange={handleChange}
placeholder="1:45:30"
/>
</label>
<label className="race-form__field">
<span className="race-form__label">Финишное время</span>
<input
className="race-form__input"
type="text"
name="finishTime"
value={form.finishTime}
onChange={handleChange}
placeholder="1:45:30"
/>
</label>
<label className="race-form__field">
<span className="race-form__label">Место на финише</span>
<input
className="race-form__input"
type="text"
name="finishPlace"
value={form.finishPlace}
onChange={handleChange}
placeholder="12/340"
/>
</label>
</fieldset>
<label className="race-form__field">
<span className="race-form__label">Место на финише</span>
<input
className="race-form__input"
type="text"
name="finishPlace"
value={form.finishPlace}
onChange={handleChange}
placeholder="12/340"
/>
</label>
</fieldset>
) : null}
<fieldset className="race-form__group">
<legend className="race-form__legend">Дополнительно</legend>

View File

@@ -10,7 +10,8 @@ import {
getRaceStatusClassName,
getRaceStatusLabel,
parseRaceDate,
splitRacesByDate,
sortByDateAsc,
sortByDateDesc,
} from "../lib";
const MONTH_OPTIONS: { value: string; label: string }[] = [
@@ -220,7 +221,32 @@ export function RacesPage(): JSX.Element {
};
}, [listQuery]);
const { upcoming, past } = useMemo(() => splitRacesByDate(races), [races]);
const { upcoming, completed } = useMemo(
() => ({
upcoming: sortByDateAsc(races.filter((race) => race.status !== "completed")),
completed: sortByDateDesc(races.filter((race) => race.status === "completed")),
}),
[races],
);
const statusMessage = useMemo(() => {
if (errorMessage && !isLoading) {
return errorMessage;
}
if (isLoading) {
return "Загружаем данные...";
}
if (viewMode === "calendar" && monthFilter === "") {
return "Выберите месяц, чтобы увидеть его крупным планом.";
}
return "";
}, [errorMessage, isLoading, monthFilter, viewMode]);
const statusClassName = [
"races-status__message",
!statusMessage ? "races-status__message--empty" : "",
errorMessage && !isLoading ? "races-status__message--error" : "",
]
.filter(Boolean)
.join(" ");
if (errorMessage && races.length === 0 && !isLoading) {
return (
@@ -294,26 +320,21 @@ export function RacesPage(): JSX.Element {
</div>
</section>
{errorMessage && !isLoading ? (
<p className="page__subtitle page__subtitle--error" role="alert" style={{ marginTop: "var(--space-4)" }}>
{errorMessage}
<div className="races-status" aria-live="polite">
<p
className={statusClassName}
role={errorMessage && !isLoading ? "alert" : undefined}
aria-busy={isLoading || undefined}
aria-hidden={!statusMessage || undefined}
>
{statusMessage || "\u00a0"}
</p>
) : null}
{viewMode === "calendar" && monthFilter === "" ? (
<p className="page__subtitle races-cal__filter-hint">Выберите месяц, чтобы увидеть его крупным планом.</p>
) : null}
{isLoading ? (
<p className="page__subtitle" aria-busy="true">
Загружаем данные...
</p>
) : null}
</div>
{viewMode === "list" ? (
<div className="race-lists">
<RaceList title="Будущие" races={upcoming} />
<RaceList title="Прошедшие" races={past} />
<RaceList title="Завершенные" races={completed} />
</div>
) : (
<div className="races-cal-wrap">