UI: выбор фракции — выпадашка в стиле выбора игрока, без занятых фракций
PlayerSelect обобщён в PickerSelect (рендер опций задаёт родитель) — им оформлены оба поля создания партии. Фракции, выбранные в других строках, не предлагаются (сервер при рандоме и так их исключал); набор фракций по-прежнему приходит из available_factions группы с учётом дополнений. Выбранное значение ищется в полном списке, а не в отфильтрованном: после «Рандом всем» с повторами фракция строки может быть занята соседней. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,8 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ApiError } from "../api/client";
|
||||
import { PlayerSelect } from "../components/PlayerSelect";
|
||||
import { Avatar } from "../components/Avatar";
|
||||
import { PickerSelect } from "../components/PickerSelect";
|
||||
import { Spinner } from "../components/Spinner";
|
||||
import { MAX_MATCH_PLAYERS } from "../domain/constants";
|
||||
import { useToast } from "../context/ToastContext";
|
||||
@@ -168,6 +169,20 @@ export function CreateMatchPage() {
|
||||
const memberOptions = (members ?? []) as MemberRead[];
|
||||
const spinning = Object.values(spin).some((v) => v != null);
|
||||
|
||||
// Опции выпадашек. Игрок — аватар + ник (в триггере аватар мельче, чтобы
|
||||
// поле не разъезжалось по высоте с соседними).
|
||||
const playerOptions = memberOptions.map((m) => ({
|
||||
id: m.user_id,
|
||||
nickname: m.nickname,
|
||||
avatar_url: m.avatar_url,
|
||||
}));
|
||||
const playerLabel = (m: { nickname: string; avatar_url?: string | null }, size: number) => (
|
||||
<>
|
||||
<Avatar url={m.avatar_url} nickname={m.nickname} size={size} />
|
||||
<span className="nick">{m.nickname}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="card">
|
||||
@@ -189,43 +204,26 @@ export function CreateMatchPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PlayerSelect
|
||||
value={row.user_id}
|
||||
options={memberOptions
|
||||
.filter((m) => !takenUserIds(idx).includes(m.user_id))
|
||||
.map((m) => ({
|
||||
user_id: m.user_id,
|
||||
nickname: m.nickname,
|
||||
avatar_url: m.avatar_url,
|
||||
}))}
|
||||
onPick={(id) => update(idx, { user_id: id })}
|
||||
<PickerSelect
|
||||
selected={playerOptions.find((m) => m.id === row.user_id) ?? null}
|
||||
options={playerOptions.filter((m) => !takenUserIds(idx).includes(m.id))}
|
||||
placeholder="— игрок —"
|
||||
renderOption={(m) => playerLabel(m, 28)}
|
||||
renderValue={(m) => playerLabel(m, 24)}
|
||||
onPick={(m) => update(idx, { user_id: m.id })}
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
{spin[idx] != null ? (
|
||||
<div className="faction-spin" style={{ flex: 1 }}>{spin[idx]}</div>
|
||||
) : (
|
||||
<select
|
||||
value={row.faction_id}
|
||||
onChange={(e) =>
|
||||
update(idx, {
|
||||
faction_id: e.target.value ? Number(e.target.value) : "",
|
||||
was_random: false,
|
||||
})
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{/* disabled+hidden: в закрытом select это плейсхолдер, в раскрытом
|
||||
списке варианта «— фракция —» нет. */}
|
||||
<option value="" disabled hidden>
|
||||
— фракция —
|
||||
</option>
|
||||
{(factions ?? []).map((f) => (
|
||||
<option key={f.id} value={f.id}>
|
||||
{f.name_ru}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<PickerSelect
|
||||
selected={(factions ?? []).find((f) => f.id === row.faction_id) ?? null}
|
||||
options={(factions ?? []).filter((f) => !takenFactionIds(idx).includes(f.id))}
|
||||
placeholder="— фракция —"
|
||||
renderOption={(f) => f.name_ru}
|
||||
onPick={(f) => update(idx, { faction_id: f.id, was_random: false })}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
className="btn"
|
||||
|
||||
Reference in New Issue
Block a user