Edit roster initial

This commit is contained in:
Kalle 2026-04-04 14:48:54 +03:00
parent 2a19e6bb79
commit 61979727ff
4 changed files with 166 additions and 19 deletions

View File

@ -80,6 +80,19 @@
text-transform: uppercase;
}
.rosterEditCount {
font-size: var(--font-xs);
color: var(--color-text-high);
margin-block-end: var(--s-2);
margin-inline-start: 12px;
}
.rosterEditButtons {
display: flex;
gap: var(--s-2);
margin-block-start: var(--s-4);
}
.teamOneDot {
border-radius: 100%;
background-color: var(--color-accent);

View File

@ -1,6 +1,7 @@
import { Armchair, DoorOpen, Tally5, Users } from "lucide-react";
import { Armchair, DoorOpen, Edit, Tally5, Users } from "lucide-react";
import { QRCodeSVG } from "qrcode.react";
import type * as React from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useSearchParams } from "react-router";
import { Alert } from "~/components/Alert";
@ -77,6 +78,7 @@ export function MatchTabs({ children, tabs }: MatchTabsProps) {
interface RosterTabTeam {
team?: {
id: number;
name: string;
url: string;
avatar?: string;
@ -88,14 +90,38 @@ interface RosterTabTeam {
interface MatchRosterTabProps {
teams: [RosterTabTeam, RosterTabTeam];
minMembersPerTeam: number;
canEditSubbedOut?: [boolean, boolean];
onSubbedOutChange?: (teamId: number, subbedOut: number[]) => void;
isSubmitting?: boolean;
}
export function MatchRosterTab({ teams }: MatchRosterTabProps) {
export function MatchRosterTab({
teams,
minMembersPerTeam,
canEditSubbedOut,
onSubbedOutChange,
isSubmitting,
}: MatchRosterTabProps) {
return (
<SendouTabPanel id={TAB_KEYS.ROSTERS}>
<div className={styles.rosters}>
<TeamRoster team={teams[0]} side="alpha" />
<TeamRoster team={teams[1]} side="bravo" />
<TeamRoster
team={teams[0]}
side="alpha"
canEditSubbedOut={canEditSubbedOut?.[0] ?? false}
minMembersPerTeam={minMembersPerTeam}
onSubbedOutChange={onSubbedOutChange}
isSubmitting={isSubmitting}
/>
<TeamRoster
team={teams[1]}
side="bravo"
canEditSubbedOut={canEditSubbedOut?.[1] ?? false}
minMembersPerTeam={minMembersPerTeam}
onSubbedOutChange={onSubbedOutChange}
isSubmitting={isSubmitting}
/>
</div>
</SendouTabPanel>
);
@ -104,10 +130,22 @@ export function MatchRosterTab({ teams }: MatchRosterTabProps) {
function TeamRoster({
team,
side,
canEditSubbedOut,
minMembersPerTeam,
onSubbedOutChange,
isSubmitting,
}: {
team: RosterTabTeam;
side: "alpha" | "bravo";
canEditSubbedOut: boolean;
minMembersPerTeam: number;
onSubbedOutChange?: (teamId: number, subbedOut: number[]) => void;
isSubmitting?: boolean;
}) {
const { t } = useTranslation(["common"]);
const [isEditing, setIsEditing] = useState(false);
const [selectedMemberIds, setSelectedMemberIds] = useState<number[]>([]);
const dotClassName = side === "alpha" ? styles.teamOneDot : styles.teamTwoDot;
const label = side === "alpha" ? "Alpha" : "Bravo";
@ -119,6 +157,8 @@ function TeamRoster({
subbedOutSet.has(member.id),
);
const showEditButton = canEditSubbedOut && team.team && !isEditing;
return (
<div className="stack xxs">
{team.team ? (
@ -139,26 +179,102 @@ function TeamRoster({
) : null}
{team.members.length > 0 ? (
<ul className={styles.rosterMembers}>
{activeMembers.map((member) => (
<li key={member.id}>
<Link
to={userPage(member)}
className="stack horizontal sm items-center"
>
<Avatar user={member} size="xxs" />
<span>{member.username}</span>
</Link>
</li>
))}
{subbedOutMembers.length > 0 ? (
{isEditing
? team.members.map((member) => (
<li key={member.id}>
<label className="stack horizontal sm items-center cursor-pointer">
<input
type="checkbox"
checked={selectedMemberIds.includes(member.id)}
onChange={() => handleToggleMember(member.id)}
/>
<Avatar user={member} size="xxs" />
<span>{member.username}</span>
</label>
</li>
))
: activeMembers.map((member) => (
<li key={member.id}>
<Link
to={userPage(member)}
className="stack horizontal sm items-center"
>
<Avatar user={member} size="xxs" />
<span>{member.username}</span>
</Link>
</li>
))}
{!isEditing && subbedOutMembers.length > 0 ? (
<li>
<SubbedOutPopover members={subbedOutMembers} />
</li>
) : null}
</ul>
) : null}
{isEditing ? (
<div>
<div className={styles.rosterEditCount}>
{selectedMemberIds.length}/{minMembersPerTeam}
</div>
<div className={styles.rosterEditButtons}>
<SendouButton
variant="primary"
size="small"
isDisabled={
isSubmitting || selectedMemberIds.length !== minMembersPerTeam
}
onPress={handleSubmit}
>
{t("common:actions.submit")}
</SendouButton>
<SendouButton
variant="outlined"
size="small"
onPress={handleCancel}
>
{t("common:actions.cancel")}
</SendouButton>
</div>
</div>
) : null}
{showEditButton ? (
<SendouButton
icon={<Edit />}
className="mt-4 mx-auto"
size="small"
onPress={() => {
setSelectedMemberIds(activeMembers.map((m) => m.id));
setIsEditing(true);
}}
>
{t("common:actions.edit")}
</SendouButton>
) : null}
</div>
);
function handleToggleMember(memberId: number) {
setSelectedMemberIds((prev) =>
prev.includes(memberId)
? prev.filter((id) => id !== memberId)
: [...prev, memberId],
);
}
function handleSubmit() {
if (!team.team || !onSubbedOutChange) return;
const subbedOutIds = team.members
.filter((m) => !selectedMemberIds.includes(m.id))
.map((m) => m.id);
onSubbedOutChange(team.team.id, subbedOutIds);
setIsEditing(false);
}
function handleCancel() {
setSelectedMemberIds(activeMembers.map((m) => m.id));
setIsEditing(false);
}
}
function SubbedOutPopover({ members }: { members: Array<CommonUser> }) {
@ -167,7 +283,7 @@ function SubbedOutPopover({ members }: { members: Array<CommonUser> }) {
return (
<SendouPopover
trigger={
<SendouButton variant="minimal" size="small">
<SendouButton variant="minimal" size="small" className="h-max">
<div className={styles.subbedOutTrigger}>
<div className={styles.subbedOutIcon}>
<Armchair size={16} />

View File

@ -15,6 +15,7 @@ import {
MatchRosterTab,
MatchTabs,
} from "~/components/match-page/MatchTabs";
import { logger } from "~/utils/logger";
import type { SendouRouteHandle } from "~/utils/remix.server";
export const handle: SendouRouteHandle = {
@ -139,9 +140,18 @@ export default function MatchPageTestRoute() {
showNoSplatnetAlert
/>
<MatchRosterTab
minMembersPerTeam={4}
canEditSubbedOut={[true, false]}
onSubbedOutChange={(teamId, subbedOut) => {
logger.info("onSubbedOutChange", { teamId, subbedOut });
}}
teams={[
{
team: { name: "me in japan", url: "/t/me-in-japan" },
team: {
id: 1,
name: "me in japan",
url: "/t/me-in-japan",
},
members: [
{
id: 1,
@ -182,7 +192,11 @@ export default function MatchPageTestRoute() {
subbedOut: [9],
},
{
team: { name: "Question Mark", url: "/t/question-mark" },
team: {
id: 2,
name: "Question Mark",
url: "/t/question-mark",
},
members: [
{
id: 5,

View File

@ -155,6 +155,10 @@
height: 100%;
}
.h-max {
height: max-content;
}
.w-full {
width: 100%;
}