feat: 토너먼트 이동 탭과 개인 투자금을 표시
This commit is contained in:
@@ -35,6 +35,7 @@ const names = [
|
|||||||
'허저',
|
'허저',
|
||||||
'주태',
|
'주태',
|
||||||
longGeneralName,
|
longGeneralName,
|
||||||
|
...Array.from({ length: 48 }, (_, index) => `예선장수${index + 17}`),
|
||||||
];
|
];
|
||||||
const participants = names.map((name, index) => ({
|
const participants = names.map((name, index) => ({
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
@@ -45,13 +46,20 @@ const participants = names.map((name, index) => ({
|
|||||||
level: 10,
|
level: 10,
|
||||||
picture: 'default.jpg',
|
picture: 'default.jpg',
|
||||||
imageServer: 0,
|
imageServer: 0,
|
||||||
groupId: 10 + (index % 8),
|
groupId: Math.floor(index / 8) < 4 ? 10 + (index % 8) : index % 8,
|
||||||
groupNo: Math.floor(index / 8),
|
groupNo: Math.floor(index / 8),
|
||||||
win: 3 - (index % 2),
|
win: Math.floor(index / 8) < 4 ? 3 - (index % 2) : 7 - Math.floor(index / 8),
|
||||||
draw: index % 2,
|
draw: index % 2,
|
||||||
lose: 0,
|
lose: Math.floor(index / 8) < 4 ? 0 : Math.floor(index / 8),
|
||||||
gl: 12 - index,
|
gl: 64 - index,
|
||||||
finalRank: Math.floor(index / 8) + 1,
|
finalRank: Math.floor(index / 8) + 1,
|
||||||
|
preliminaryGroupId: index % 8,
|
||||||
|
preliminaryGroupNo: Math.floor(index / 8),
|
||||||
|
preliminaryRank: Math.floor(index / 8) + 1,
|
||||||
|
preliminaryWin: 7 - Math.floor(index / 8),
|
||||||
|
preliminaryDraw: index % 2,
|
||||||
|
preliminaryLose: Math.floor(index / 8),
|
||||||
|
preliminaryGl: 64 - index,
|
||||||
}));
|
}));
|
||||||
const matches = [
|
const matches = [
|
||||||
...Array.from({ length: 8 }, (_, index) => ({
|
...Array.from({ length: 8 }, (_, index) => ({
|
||||||
@@ -181,11 +189,11 @@ const installFixture = async (page: Page, options: { applicationOpen?: boolean }
|
|||||||
if (operation === 'tournament.getBettingSummary') {
|
if (operation === 'tournament.getBettingSummary') {
|
||||||
return response({
|
return response({
|
||||||
totals: Object.fromEntries(
|
totals: Object.fromEntries(
|
||||||
participants.map((participant, index) => [participant.id, 100 + index * 10])
|
participants.slice(0, 16).map((participant, index) => [participant.id, 100 + index * 10])
|
||||||
),
|
),
|
||||||
myTotals: {},
|
myTotals: { 1: 120, 2: 40 },
|
||||||
totalAmount: 2800,
|
totalAmount: 2800,
|
||||||
myAmount: 0,
|
myAmount: 160,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (operation === 'tournament.getRankings') {
|
if (operation === 'tournament.getRankings') {
|
||||||
@@ -297,6 +305,14 @@ test('desktop bracket connects every real general slot to the next round', async
|
|||||||
expect(oddsContainment.cardHeight).toBeGreaterThanOrEqual(82);
|
expect(oddsContainment.cardHeight).toBeGreaterThanOrEqual(82);
|
||||||
expect(oddsContainment.oddsTop).toBeGreaterThanOrEqual(oddsContainment.cardTop);
|
expect(oddsContainment.oddsTop).toBeGreaterThanOrEqual(oddsContainment.cardTop);
|
||||||
expect(oddsContainment.oddsBottom).toBeLessThanOrEqual(oddsContainment.cardBottom);
|
expect(oddsContainment.oddsBottom).toBeLessThanOrEqual(oddsContainment.cardBottom);
|
||||||
|
await expect(firstSlot.locator('.bracket-my-bet')).toHaveText('내 투자 금120');
|
||||||
|
|
||||||
|
const preliminaryTables = page.locator('.preliminary-grid table');
|
||||||
|
await expect(preliminaryTables).toHaveCount(8);
|
||||||
|
for (let groupIndex = 0; groupIndex < 8; groupIndex += 1) {
|
||||||
|
await expect(preliminaryTables.nth(groupIndex).locator('tbody tr')).toHaveCount(8);
|
||||||
|
await expect(preliminaryTables.nth(groupIndex).locator('.general-identity')).toHaveCount(8);
|
||||||
|
}
|
||||||
|
|
||||||
await persistScreenshot(page, 'tournament-desktop', testInfo.outputPath('tournament-bracket-desktop.webp'));
|
await persistScreenshot(page, 'tournament-desktop', testInfo.outputPath('tournament-bracket-desktop.webp'));
|
||||||
});
|
});
|
||||||
@@ -413,6 +429,7 @@ test('mobile bracket exposes every round through tabs with standard horizontal i
|
|||||||
});
|
});
|
||||||
expect(mobileOddsContainment.cardHeight).toBeGreaterThanOrEqual(82);
|
expect(mobileOddsContainment.cardHeight).toBeGreaterThanOrEqual(82);
|
||||||
expect(mobileOddsContainment.oddsBottom).toBeLessThanOrEqual(mobileOddsContainment.cardBottom);
|
expect(mobileOddsContainment.oddsBottom).toBeLessThanOrEqual(mobileOddsContainment.cardBottom);
|
||||||
|
await expect(firstMobileSlot.locator('.bracket-my-bet')).toHaveText('내 투자 금120');
|
||||||
await expect(page.getByRole('tablist', { name: '본선 조 선택' })).toBeVisible();
|
await expect(page.getByRole('tablist', { name: '본선 조 선택' })).toBeVisible();
|
||||||
await page.getByRole('tab', { name: '二조' }).first().click();
|
await page.getByRole('tab', { name: '二조' }).first().click();
|
||||||
await expect(page.getByRole('tab', { name: '二조' }).first()).toHaveAttribute('aria-selected', 'true');
|
await expect(page.getByRole('tab', { name: '二조' }).first()).toHaveAttribute('aria-selected', 'true');
|
||||||
@@ -420,12 +437,39 @@ test('mobile bracket exposes every round through tabs with standard horizontal i
|
|||||||
await persistScreenshot(page, 'tournament-mobile', testInfo.outputPath('tournament-bracket-mobile.webp'));
|
await persistScreenshot(page, 'tournament-mobile', testInfo.outputPath('tournament-bracket-mobile.webp'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('tournament and betting pages expose same-row navigation tabs beside close', async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await openTournament(page);
|
||||||
|
|
||||||
|
const navigation = page.getByRole('tablist', { name: '토너먼트와 베팅장 이동' });
|
||||||
|
const tournamentTab = navigation.getByRole('tab', { name: '토너먼트' });
|
||||||
|
const bettingTab = navigation.getByRole('tab', { name: '베팅장' });
|
||||||
|
const close = page.getByRole('button', { name: '창 닫기' }).first();
|
||||||
|
await expect(tournamentTab).toHaveAttribute('aria-selected', 'true');
|
||||||
|
|
||||||
|
const headerCenters = await Promise.all(
|
||||||
|
[tournamentTab, bettingTab, close].map(async (control) => {
|
||||||
|
const box = await control.boundingBox();
|
||||||
|
return box ? box.y + box.height / 2 : -1;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(Math.max(...headerCenters) - Math.min(...headerCenters)).toBeLessThan(1);
|
||||||
|
|
||||||
|
await bettingTab.click();
|
||||||
|
await expect(page).toHaveURL(/\/betting$/);
|
||||||
|
await expect(page.getByRole('tab', { name: '베팅장' })).toHaveAttribute('aria-selected', 'true');
|
||||||
|
await page.getByRole('tab', { name: '토너먼트' }).click();
|
||||||
|
await expect(page).toHaveURL(/\/tournament$/);
|
||||||
|
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
|
||||||
|
});
|
||||||
|
|
||||||
test('mobile betting rankings use tabs and keep dedicated icons beside general names', async ({ page }, testInfo) => {
|
test('mobile betting rankings use tabs and keep dedicated icons beside general names', async ({ page }, testInfo) => {
|
||||||
await page.setViewportSize({ width: 390, height: 844 });
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
await installFixture(page);
|
await installFixture(page);
|
||||||
await page.goto('betting');
|
await page.goto('betting');
|
||||||
|
|
||||||
await expect(page.locator('.candidate-card')).toHaveCount(16);
|
await expect(page.locator('.candidate-card')).toHaveCount(16);
|
||||||
|
await expect(page.locator('.betting-bracket .bracket-my-bet').first()).toHaveText('내 투자 금120');
|
||||||
await expect(page.getByRole('tablist', { name: '토너먼트 랭킹 종목 선택' })).toBeVisible();
|
await expect(page.getByRole('tablist', { name: '토너먼트 랭킹 종목 선택' })).toBeVisible();
|
||||||
await expect(page.locator('.ranking-table:visible')).toHaveCount(1);
|
await expect(page.locator('.ranking-table:visible')).toHaveCount(1);
|
||||||
await page.getByRole('tab', { name: '통솔전' }).click();
|
await page.getByRole('tab', { name: '통솔전' }).click();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const props = defineProps<{
|
|||||||
matches: TournamentBracketMatch[];
|
matches: TournamentBracketMatch[];
|
||||||
winnerId?: number;
|
winnerId?: number;
|
||||||
betTotals?: Record<number, number>;
|
betTotals?: Record<number, number>;
|
||||||
|
myBetTotals?: Record<number, number>;
|
||||||
totalBet: number;
|
totalBet: number;
|
||||||
showLegend?: boolean;
|
showLegend?: boolean;
|
||||||
}>();
|
}>();
|
||||||
@@ -66,6 +67,7 @@ const odds = (id: number | null) => {
|
|||||||
if (!amount) return '∞';
|
if (!amount) return '∞';
|
||||||
return (props.totalBet / amount).toFixed(2);
|
return (props.totalBet / amount).toFixed(2);
|
||||||
};
|
};
|
||||||
|
const myBet = (id: number | null) => (id === null ? 0 : (props.myBetTotals?.[id] ?? 0));
|
||||||
const mobilePairs = computed(() => {
|
const mobilePairs = computed(() => {
|
||||||
const column = roundColumns.value[activeMobileRound.value] ?? [];
|
const column = roundColumns.value[activeMobileRound.value] ?? [];
|
||||||
if (activeMobileRound.value === roundColumns.value.length - 1) return column.map((slot) => [slot]);
|
if (activeMobileRound.value === roundColumns.value.length - 1) return column.map((slot) => [slot]);
|
||||||
@@ -116,7 +118,10 @@ const mobilePairs = computed(() => {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<GeneralIdentity :name="slot.name" :picture="slot.picture" :image-server="slot.imageServer" />
|
<GeneralIdentity :name="slot.name" :picture="slot.picture" :image-server="slot.imageServer" />
|
||||||
<small v-if="columnIndex === 0" class="bracket-odds">배당 {{ odds(slot.id) }}</small>
|
<div v-if="columnIndex === 0" class="bracket-bet-summary">
|
||||||
|
<small class="bracket-odds">배당 {{ odds(slot.id) }}</small>
|
||||||
|
<small class="bracket-my-bet">내 투자 금{{ myBet(slot.id) }}</small>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +151,10 @@ const mobilePairs = computed(() => {
|
|||||||
:data-general-id="slot.id ?? undefined"
|
:data-general-id="slot.id ?? undefined"
|
||||||
>
|
>
|
||||||
<GeneralIdentity :name="slot.name" :picture="slot.picture" :image-server="slot.imageServer" />
|
<GeneralIdentity :name="slot.name" :picture="slot.picture" :image-server="slot.imageServer" />
|
||||||
<small v-if="activeMobileRound === 0" class="bracket-odds">배당 {{ odds(slot.id) }}</small>
|
<div v-if="activeMobileRound === 0" class="bracket-bet-summary">
|
||||||
|
<small class="bracket-odds">배당 {{ odds(slot.id) }}</small>
|
||||||
|
<small class="bracket-my-bet">내 투자 금{{ myBet(slot.id) }}</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<strong v-if="pair.length === 2" class="versus" aria-hidden="true">VS</strong>
|
<strong v-if="pair.length === 2" class="versus" aria-hidden="true">VS</strong>
|
||||||
</article>
|
</article>
|
||||||
@@ -220,12 +228,26 @@ const mobilePairs = computed(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
.bracket-odds {
|
.bracket-bet-summary {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.bracket-odds,
|
||||||
|
.bracket-my-bet {
|
||||||
display: block;
|
display: block;
|
||||||
|
min-width: 0;
|
||||||
color: skyblue;
|
color: skyblue;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
text-align: right;
|
}
|
||||||
|
.bracket-my-bet {
|
||||||
|
overflow: hidden;
|
||||||
|
color: orange;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.mobile-bracket {
|
.mobile-bracket {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
activePage: 'tournament' | 'betting';
|
||||||
|
title: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header class="tournament-page-header">
|
||||||
|
<strong class="tournament-page-title">{{ title }}</strong>
|
||||||
|
<div class="tournament-page-actions">
|
||||||
|
<nav class="tournament-page-tabs" role="tablist" aria-label="토너먼트와 베팅장 이동">
|
||||||
|
<RouterLink v-slot="{ navigate }" custom to="/tournament">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="activePage === 'tournament'"
|
||||||
|
:class="{ active: activePage === 'tournament' }"
|
||||||
|
@click="navigate"
|
||||||
|
>
|
||||||
|
토너먼트
|
||||||
|
</button>
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink v-slot="{ navigate }" custom to="/betting">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="activePage === 'betting'"
|
||||||
|
:class="{ active: activePage === 'betting' }"
|
||||||
|
@click="navigate"
|
||||||
|
>
|
||||||
|
베팅장
|
||||||
|
</button>
|
||||||
|
</RouterLink>
|
||||||
|
</nav>
|
||||||
|
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||||
|
<button class="close-button" type="button" @click="navigate">창 닫기</button>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tournament-page-header {
|
||||||
|
display: flex;
|
||||||
|
min-height: 48px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
.tournament-page-title {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tournament-page-actions,
|
||||||
|
.tournament-page-tabs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
height: 44px;
|
||||||
|
margin: 0;
|
||||||
|
border: 1px solid #666;
|
||||||
|
border-radius: 5.25px;
|
||||||
|
background: #444;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tournament-page-tabs button {
|
||||||
|
min-width: 72px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
.tournament-page-tabs button.active {
|
||||||
|
border-color: #f39c12;
|
||||||
|
background: #8a5b13;
|
||||||
|
}
|
||||||
|
.close-button {
|
||||||
|
width: 88px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-color: #375a7f;
|
||||||
|
background: #375a7f;
|
||||||
|
}
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
filter: brightness(1.25);
|
||||||
|
}
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 2px solid #f39c12;
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.tournament-page-header {
|
||||||
|
gap: 4px;
|
||||||
|
padding-inline: 2px;
|
||||||
|
}
|
||||||
|
.tournament-page-title {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.tournament-page-actions,
|
||||||
|
.tournament-page-tabs {
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.tournament-page-tabs button {
|
||||||
|
min-width: 68px;
|
||||||
|
padding-inline: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { formatServerDateTime } from '@sammo-ts/common';
|
import { formatServerDateTime } from '@sammo-ts/common';
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import TournamentBracket from '../components/tournament/TournamentBracket.vue';
|
import TournamentBracket from '../components/tournament/TournamentBracket.vue';
|
||||||
|
import TournamentPageHeader from '../components/tournament/TournamentPageHeader.vue';
|
||||||
import GeneralIdentity from '../components/ui/GeneralIdentity.vue';
|
import GeneralIdentity from '../components/ui/GeneralIdentity.vue';
|
||||||
import { trpc } from '../utils/trpc';
|
import { trpc } from '../utils/trpc';
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ const candidates = computed(() =>
|
|||||||
const totalAmount = computed(() => summary.value?.totalAmount ?? 0);
|
const totalAmount = computed(() => summary.value?.totalAmount ?? 0);
|
||||||
const myAmount = computed(() => summary.value?.myAmount ?? 0);
|
const myAmount = computed(() => summary.value?.myAmount ?? 0);
|
||||||
const betTotals = computed(() => summary.value?.totals as Record<number, number> | undefined);
|
const betTotals = computed(() => summary.value?.totals as Record<number, number> | undefined);
|
||||||
|
const myBetTotals = computed(() => summary.value?.myTotals as Record<number, number> | undefined);
|
||||||
const ratio = (id: number) => {
|
const ratio = (id: number) => {
|
||||||
const totals = summary.value?.totals as Record<number, number> | undefined;
|
const totals = summary.value?.totals as Record<number, number> | undefined;
|
||||||
const amount = totals?.[id] ?? 0;
|
const amount = totals?.[id] ?? 0;
|
||||||
@@ -110,12 +112,7 @@ const placeBet = async (targetId: number) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main id="tournament-betting-container" class="betting-page">
|
<main id="tournament-betting-container" class="betting-page">
|
||||||
<section class="title bg0">
|
<TournamentPageHeader class="bg0" active-page="betting" title="베 팅 장" />
|
||||||
베 팅 장<br />
|
|
||||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
|
||||||
<button class="close-button" type="button" @click="navigate">창 닫기</button>
|
|
||||||
</RouterLink>
|
|
||||||
</section>
|
|
||||||
<section class="toolbar bg0">
|
<section class="toolbar bg0">
|
||||||
<button type="button" @click="load">갱신</button>
|
<button type="button" @click="load">갱신</button>
|
||||||
<span v-if="loading">불러오는 중...</span>
|
<span v-if="loading">불러오는 중...</span>
|
||||||
@@ -138,6 +135,7 @@ const placeBet = async (targetId: number) => {
|
|||||||
:matches="snapshot?.matches ?? []"
|
:matches="snapshot?.matches ?? []"
|
||||||
:winner-id="snapshot?.state?.winnerId"
|
:winner-id="snapshot?.state?.winnerId"
|
||||||
:bet-totals="betTotals"
|
:bet-totals="betTotals"
|
||||||
|
:my-bet-totals="myBetTotals"
|
||||||
:total-bet="totalAmount"
|
:total-bet="totalAmount"
|
||||||
:show-legend="false"
|
:show-legend="false"
|
||||||
/>
|
/>
|
||||||
@@ -303,25 +301,6 @@ const placeBet = async (targetId: number) => {
|
|||||||
.bg2 {
|
.bg2 {
|
||||||
background: #142b42 var(--sammo-texture-blue);
|
background: #142b42 var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.title {
|
|
||||||
min-height: 68px;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 19.1875px;
|
|
||||||
}
|
|
||||||
.close-button {
|
|
||||||
display: block;
|
|
||||||
width: 88px;
|
|
||||||
height: 44px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border: 1px solid #375a7f;
|
|
||||||
border-radius: 5.25px;
|
|
||||||
background: #375a7f;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 18px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
@@ -417,10 +396,6 @@ button:hover,
|
|||||||
button:focus {
|
button:focus {
|
||||||
filter: brightness(1.25);
|
filter: brightness(1.25);
|
||||||
}
|
}
|
||||||
.close-button:hover,
|
|
||||||
.close-button:focus {
|
|
||||||
filter: brightness(1.2);
|
|
||||||
}
|
|
||||||
button:focus-visible,
|
button:focus-visible,
|
||||||
select:focus-visible {
|
select:focus-visible {
|
||||||
outline: 2px solid #f39c12;
|
outline: 2px solid #f39c12;
|
||||||
@@ -507,10 +482,6 @@ select:disabled {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.title {
|
|
||||||
height: auto;
|
|
||||||
min-height: 55px;
|
|
||||||
}
|
|
||||||
.state {
|
.state {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { formatServerDateTime } from '@sammo-ts/common';
|
import { formatServerDateTime } from '@sammo-ts/common';
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import TournamentBracket from '../components/tournament/TournamentBracket.vue';
|
import TournamentBracket from '../components/tournament/TournamentBracket.vue';
|
||||||
|
import TournamentPageHeader from '../components/tournament/TournamentPageHeader.vue';
|
||||||
import GeneralIdentity from '../components/ui/GeneralIdentity.vue';
|
import GeneralIdentity from '../components/ui/GeneralIdentity.vue';
|
||||||
import { trpc } from '../utils/trpc';
|
import { trpc } from '../utils/trpc';
|
||||||
import { resolveTournamentStageName } from '../utils/tournamentStatus';
|
import { resolveTournamentStageName } from '../utils/tournamentStatus';
|
||||||
@@ -58,6 +59,7 @@ const openingTime = computed(() =>
|
|||||||
formatServerDateTime(snapshot.value?.state?.nextAt, { format: 'hourMinute', fallback: '--:--' })
|
formatServerDateTime(snapshot.value?.state?.nextAt, { format: 'hourMinute', fallback: '--:--' })
|
||||||
);
|
);
|
||||||
const betTotals = computed(() => betting.value?.totals as Record<number, number> | undefined);
|
const betTotals = computed(() => betting.value?.totals as Record<number, number> | undefined);
|
||||||
|
const myBetTotals = computed(() => betting.value?.myTotals as Record<number, number> | undefined);
|
||||||
const isParticipant = computed(() =>
|
const isParticipant = computed(() =>
|
||||||
(snapshot.value?.participants ?? []).some((participant) => participant.id === myGeneralId.value)
|
(snapshot.value?.participants ?? []).some((participant) => participant.id === myGeneralId.value)
|
||||||
);
|
);
|
||||||
@@ -71,8 +73,25 @@ const groups = computed(() =>
|
|||||||
const preliminaryGroups = computed(() =>
|
const preliminaryGroups = computed(() =>
|
||||||
Array.from({ length: 8 }, (_, index) =>
|
Array.from({ length: 8 }, (_, index) =>
|
||||||
(snapshot.value?.participants ?? [])
|
(snapshot.value?.participants ?? [])
|
||||||
.filter((participant) => participant.groupId === index)
|
.filter((participant) => {
|
||||||
.sort((a, b) => (a.seedRank ?? 99) - (b.seedRank ?? 99) || (a.groupNo ?? 99) - (b.groupNo ?? 99))
|
const groupId =
|
||||||
|
participant.preliminaryGroupId ??
|
||||||
|
(participant.groupId !== undefined && participant.groupId < 8 ? participant.groupId : undefined);
|
||||||
|
return groupId === index;
|
||||||
|
})
|
||||||
|
.map((participant) => ({
|
||||||
|
...participant,
|
||||||
|
groupNo: participant.preliminaryGroupNo ?? participant.groupNo,
|
||||||
|
win: participant.preliminaryWin ?? participant.win,
|
||||||
|
draw: participant.preliminaryDraw ?? participant.draw,
|
||||||
|
lose: participant.preliminaryLose ?? participant.lose,
|
||||||
|
gl: participant.preliminaryGl ?? participant.gl,
|
||||||
|
}))
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(a.preliminaryRank ?? a.seedRank ?? 99) - (b.preliminaryRank ?? b.seedRank ?? 99) ||
|
||||||
|
(a.groupNo ?? 99) - (b.groupNo ?? 99)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const groupNames = ['一', '二', '三', '四', '五', '六', '七', '八'];
|
const groupNames = ['一', '二', '三', '四', '五', '六', '七', '八'];
|
||||||
@@ -141,12 +160,7 @@ const start = async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main id="tournament-container" class="legacy-page">
|
<main id="tournament-container" class="legacy-page">
|
||||||
<section class="legacy-title bg0">
|
<TournamentPageHeader class="bg0" active-page="tournament" title="삼모전 토너먼트" />
|
||||||
<div>삼모전 토너먼트</div>
|
|
||||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
|
||||||
<button class="close-button" type="button" @click="navigate">창 닫기</button>
|
|
||||||
</RouterLink>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="toolbar bg0">
|
<section class="toolbar bg0">
|
||||||
<button type="button" @click="load">갱신</button>
|
<button type="button" @click="load">갱신</button>
|
||||||
@@ -177,6 +191,7 @@ const start = async () => {
|
|||||||
:matches="snapshot?.matches ?? []"
|
:matches="snapshot?.matches ?? []"
|
||||||
:winner-id="snapshot?.state?.winnerId"
|
:winner-id="snapshot?.state?.winnerId"
|
||||||
:bet-totals="betTotals"
|
:bet-totals="betTotals"
|
||||||
|
:my-bet-totals="myBetTotals"
|
||||||
:total-bet="totalBet"
|
:total-bet="totalBet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -384,25 +399,6 @@ const start = async () => {
|
|||||||
.bg2 {
|
.bg2 {
|
||||||
background: #142b42 var(--sammo-texture-blue);
|
background: #142b42 var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.legacy-title {
|
|
||||||
min-height: 68px;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 19.1875px;
|
|
||||||
}
|
|
||||||
.close-button {
|
|
||||||
display: block;
|
|
||||||
width: 88px;
|
|
||||||
height: 44px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border: 1px solid #375a7f;
|
|
||||||
border-radius: 5.25px;
|
|
||||||
background: #375a7f;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 18px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
@@ -433,10 +429,6 @@ button:hover,
|
|||||||
button:focus {
|
button:focus {
|
||||||
filter: brightness(1.25);
|
filter: brightness(1.25);
|
||||||
}
|
}
|
||||||
.close-button:hover,
|
|
||||||
.close-button:focus {
|
|
||||||
filter: brightness(1.2);
|
|
||||||
}
|
|
||||||
button:focus-visible {
|
button:focus-visible {
|
||||||
outline: 2px solid #f39c12;
|
outline: 2px solid #f39c12;
|
||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
@@ -532,10 +524,6 @@ td {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.legacy-title {
|
|
||||||
height: auto;
|
|
||||||
min-height: 55px;
|
|
||||||
}
|
|
||||||
.state-row {
|
.state-row {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user