diff --git a/app/components/match-page/MatchTabs.module.css b/app/components/match-page/MatchTabs.module.css index 2c1252bb5..c158b17f9 100644 --- a/app/components/match-page/MatchTabs.module.css +++ b/app/components/match-page/MatchTabs.module.css @@ -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); diff --git a/app/components/match-page/MatchTabs.tsx b/app/components/match-page/MatchTabs.tsx index 3164f9b03..669249fa0 100644 --- a/app/components/match-page/MatchTabs.tsx +++ b/app/components/match-page/MatchTabs.tsx @@ -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 (
- - + +
); @@ -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([]); + 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 (
{team.team ? ( @@ -139,26 +179,102 @@ function TeamRoster({ ) : null} {team.members.length > 0 ? ( ) : null} + {isEditing ? ( +
+
+ {selectedMemberIds.length}/{minMembersPerTeam} +
+
+ + {t("common:actions.submit")} + + + {t("common:actions.cancel")} + +
+
+ ) : null} + {showEditButton ? ( + } + className="mt-4 mx-auto" + size="small" + onPress={() => { + setSelectedMemberIds(activeMembers.map((m) => m.id)); + setIsEditing(true); + }} + > + {t("common:actions.edit")} + + ) : null}
); + + 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 }) { @@ -167,7 +283,7 @@ function SubbedOutPopover({ members }: { members: Array }) { return ( +
diff --git a/app/features/match-page-test/routes/match-page-test.tsx b/app/features/match-page-test/routes/match-page-test.tsx index 69493e564..7fc43d0c4 100644 --- a/app/features/match-page-test/routes/match-page-test.tsx +++ b/app/features/match-page-test/routes/match-page-test.tsx @@ -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 /> { + 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, diff --git a/app/styles/utils.css b/app/styles/utils.css index fbcd658af..6dd5ada64 100644 --- a/app/styles/utils.css +++ b/app/styles/utils.css @@ -155,6 +155,10 @@ height: 100%; } + .h-max { + height: max-content; + } + .w-full { width: 100%; }