/* parts2.jsx — расписание с фильтрами, таблицы групп, паблы, бронирование */
const { useState: useState2, useMemo } = React;
const W2 = window.WC;
const U2 = window.U;
/* ════════ РАСПИСАНИЕ ════════ */
function Schedule({ cur, favs, toggleFav }) {
const [mode, setMode] = useState2('group');
const [day, setDay] = useState2(null); // null = все дни
const [favOnly, setFavOnly] = useState2(false);
const days = W2.groupDays;
const byDay = useMemo(() => {
const map = {};
W2.matches.forEach((m) => {(map[m.date] = map[m.date] || []).push(m);});
Object.values(map).forEach((arr) => arr.sort((a, b) => a.time.localeCompare(b.time)));
return map;
}, []);
const passFav = (m) => !favOnly || favs.has(m.home) || favs.has(m.away);
const shownDays = (day ? [day] : days).filter((d) => (byDay[d] || []).some(passFav));
return (
Расписание
Все матчи турнира
setMode('group')}>Групповой этап
setMode('ko')}>Плей-офф
{mode === 'group' ?
<>
setDay(null)}>Все
{days.map((d) =>
setDay(day === d ? null : d)}>
{U2.parts(d).d}
{U2.wdShort(d)}
)}
{day ? U2.fmtLong(day) : 'Туры 1–3 · 11–27 июня'}
setFavOnly(!favOnly)}>
Избранное
{shownDays.length === 0 ?
Нет матчей с избранными командами в этот день. Отметьте команды звёздочкой ★ или снимите фильтр.
:
shownDays.map((d) =>
{U2.fmtLong(d)} · {U2.wdShort(d)}
{byDay[d].filter(passFav).map((m) =>
)}
)}
> :
{W2.knockout.map((r) =>
{r.name}
{r.dates}{r.city ? ' · ' + r.city : ''} · пары по итогам групп
{r.count}{matchWord(r.count)}
)}
}
);
}
/* ════════ ТАБЛИЦЫ ГРУПП ════════ */
function Standings({ favs }) {
const [g, setG] = useState2('A');
const keys = Object.keys(W2.groups);
const teams = W2.groups[g];
return (
Турнирная таблица
Группы A — L
{keys.map((k) =>
setG(k)}>{k}
)}
{teams.map((code, i) =>
{i + 1}
{U2.teamName(code)}
{favs.has(code) && }
0 матчей
)}
1–2 место — в плей-офф напрямую
Плюс 8 лучших сборных с 3-го места проходят в 1/16 финала.
);
}
/* ════════ ПАБЫ ════════ */
function BarMonogram({ name }) {
return {name[0]} ;
}
function Bars({ cur, onBook }) {
return (
Гастрономические пабы Петербурга. Выбирайте ближайший и бронируйте стол под нужный матч.
{W2.bars.map((b) =>
{b.name}
Гастрономический паб
{b.addr}
{b.phones.map((p) =>
{p}
)}
onBook(b)}>
Забронировать стол
)}
);
}
/* ════════ МОДАЛКА БРОНИРОВАНИЯ ════════ */
function BookingModal({ bar, cur, onClose }) {
const futureDays = W2.groupDays.filter((d) => d >= cur);
const days = futureDays.length ? futureDays : W2.groupDays;
const [date, setDate] = useState2(days[0]);
const [matchId, setMatchId] = useState2('');
const [guests, setGuests] = useState2(2);
const [name, setName] = useState2('');
const [phone, setPhone] = useState2('');
const [done, setDone] = useState2(false);
const dayMatches = U2.matchesOn(date).sort((a, b) => a.time.localeCompare(b.time));
function submit(e) {
e.preventDefault();
setDone(true);
}
return (
e.stopPropagation()}>
{done ?
Заявка принята
{bar.name} · {U2.fmtLong(date)} · {guests} {guestWord(guests)}.
Мы перезвоним для подтверждения на {phone || 'указанный номер'}.
Готово
Демо-форма. Реальная отправка не выполняется.
:
}
);
}
function guestWord(n) {
const a = n % 10,b = n % 100;
if (a === 1 && b !== 11) return 'гость';
if (a >= 2 && a <= 4 && (b < 10 || b >= 20)) return 'гостя';
return 'гостей';
}
Object.assign(window, { Schedule, Standings, Bars, BookingModal, guestWord });