feat: add score prediction and clear actions to BattleTable

- Add "Clear Current Round" and "Clear All Groups" buttons to reset specific or entire match results.
- Implement a summary Table showing current scores, predicted scores after the latest round, and score differentials.
- Import necessary Ant Design components (Button, Table) and icons (ClearOutlined).
- Refactor `buildMatchGroupTable` to return full arrays instead of filtering out falsy values.
- Disable row hover effect in GroupMember table for cleaner appearance.
This commit is contained in:
kyuuseiryuu 2026-03-11 00:48:44 +09:00
parent bb4ca8ae40
commit 56159de837
2 changed files with 57 additions and 3 deletions

View File

@ -1,9 +1,10 @@
import type { BasePlayer } from "../types"; import type { BasePlayer } from "../types";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { calculate, getRoundTable } from "../utils/common"; import { calculate, getRoundTable } from "../utils/common";
import { Card, Divider, Flex, Space } from "antd"; import { Button, Card, Divider, Flex, Space, Table } from "antd";
import styled from "styled-components"; import styled from "styled-components";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { ClearOutlined } from "@ant-design/icons";
const StyledContainer = styled(Flex)` const StyledContainer = styled(Flex)`
.winer { .winer {
@ -67,7 +68,7 @@ function BattleTable({
}); });
return cloneDeep([left, right]); return cloneDeep([left, right]);
}); });
return roundTable.filter(Boolean); return roundTable;
}, []); }, []);
const [matchGroupTable, setMatchGroupTable] = useState<ReturnType<typeof buildMatchGroupTable>>([]); const [matchGroupTable, setMatchGroupTable] = useState<ReturnType<typeof buildMatchGroupTable>>([]);
@ -82,6 +83,29 @@ function BattleTable({
} }
setMatchGroupTable(buildMatchGroupTable(playerList)); setMatchGroupTable(buildMatchGroupTable(playerList));
}, []); }, []);
const handleClear = useCallback((left?: (BasePlayer | null | undefined)[], right?: (BasePlayer | null | undefined)[]) => {
left?.forEach((l, i) => {
const r = right?.[i];
const key = `${l?.uid}-${r?.uid}`;
resultMap.delete(key);
});
setMatchGroupTable(buildMatchGroupTable(playerList));
}, [matchGroupTable, playerList]);
const handleClearAll = useCallback(() => {
matchGroupTable.forEach(([left, right]) => {
left?.forEach((l, i) => {
const r = right?.[i];
const key = `${l?.uid}-${r?.uid}`;
resultMap.delete(key);
});
});
setMatchGroupTable(buildMatchGroupTable(playerList));
}, [matchGroupTable, playerList]);
const scoreDiff = useMemo(() => {
const [left = [], right = []] = matchGroupTable?.[matchGroupTable.length - 1] ?? [];
const newScoreMap = Object.fromEntries([...left, ...right].map(e => [e?.uid, e?.score]));
return newScoreMap;
}, [playerList, matchGroupTable]);
useEffect(() => { useEffect(() => {
const roundTable = buildMatchGroupTable(playerList); const roundTable = buildMatchGroupTable(playerList);
setMatchGroupTable(roundTable); setMatchGroupTable(roundTable);
@ -91,7 +115,12 @@ function BattleTable({
<StyledContainer vertical gap={12} justify="center" align="center"> <StyledContainer vertical gap={12} justify="center" align="center">
{matchGroupTable.map(([left, right], i) => { {matchGroupTable.map(([left, right], i) => {
return ( return (
<Card size="small" key={`round-${i}`} title={`Round: ${i + 1}`}> <Card
size="small"
key={`round-${i}`}
title={`Round: ${i + 1}`}
extra={<Button type="link" onClick={() => handleClear(left, right)} icon={<ClearOutlined />}></Button>}
>
{left?.map((l, i) => { {left?.map((l, i) => {
const r = right?.[i]; const r = right?.[i];
const key = `${l?.uid}-${r?.uid}`; const key = `${l?.uid}-${r?.uid}`;
@ -115,6 +144,30 @@ function BattleTable({
</Card> </Card>
); );
})} })}
<Table
title={() => (
<Flex align="center" justify="space-between">
<span></span>
<Button type="link" icon={<ClearOutlined />} onClick={handleClearAll}></Button>
</Flex>
)}
showHeader={false}
pagination={false}
rowHoverable={false}
size="small"
dataSource={playerList}
rowKey={e => e.uid}
style={{ minWidth: 300 }}
columns={[
{ dataIndex: 'name' },
{ dataIndex: 'score' },
{ dataIndex: 'uid', render: (uid, { score }) => scoreDiff[uid] || score },
{ dataIndex: 'uid', align: 'right', render: (uid, { score }) => {
const diff = +(scoreDiff[uid] || score) - +score;
return diff > 0 ? `+${diff}` : diff;
} },
]}
/>
</StyledContainer> </StyledContainer>
); );
} }

View File

@ -30,6 +30,7 @@ export const GroupMember: React.FC<Props> = props => {
rowKey={e => `${e.id}-${e.name}-${e.score}`} rowKey={e => `${e.id}-${e.name}-${e.score}`}
showHeader={false} showHeader={false}
pagination={false} pagination={false}
rowHoverable={false}
dataSource={props.players} dataSource={props.players}
columns={[ columns={[
{ dataIndex: '_', render: (_, __, i) => `(${i + 1})` }, { dataIndex: '_', render: (_, __, i) => `(${i + 1})` },