mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-20 10:18:24 -05:00
Edit roster initial
This commit is contained in:
parent
2a19e6bb79
commit
61979727ff
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -155,6 +155,10 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.h-max {
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user