final
This commit is contained in:
@@ -74,6 +74,7 @@
|
||||
:position-boards="positionBoards"
|
||||
:live-group-code="liveMonitorGroupCode"
|
||||
:live-group-members="liveMonitorMembers"
|
||||
:prelim-tie="prelimTie"
|
||||
:prelim-tie-rows="prelimTieRows"
|
||||
:preliminary-rows="preliminaryRows"
|
||||
:finalists="finalists"
|
||||
@@ -178,6 +179,7 @@
|
||||
@remove-player-image="removePlayerImage"
|
||||
@delete-player="deletePlayer"
|
||||
@request-reset-stage="openResetStageModal"
|
||||
@reset-final-group-by-seed="resetFinalGroupBySeed"
|
||||
@save-position-board="savePositionBoard"
|
||||
@update-score-filter="updateScoreFilter"
|
||||
/>
|
||||
@@ -1021,6 +1023,39 @@ async function savePositionBoard(payload) {
|
||||
}
|
||||
}
|
||||
|
||||
async function resetFinalGroupBySeed() {
|
||||
const ranked = [...adminFinalRows.value]
|
||||
.filter((row) => Number(row.seed || 0) > 0)
|
||||
.sort((a, b) => {
|
||||
const seedA = Number(a.seed || 0)
|
||||
const seedB = Number(b.seed || 0)
|
||||
if (seedA !== seedB) return seedA - seedB
|
||||
return Number(a.playerId || 0) - Number(b.playerId || 0)
|
||||
})
|
||||
|
||||
if (ranked.length === 0) {
|
||||
showToast(t('labels.noFinalists'), 'error')
|
||||
return
|
||||
}
|
||||
|
||||
const slots = ranked.map((row, index) => ({
|
||||
playerId: row.playerId,
|
||||
groupKey: index < 6 ? '1' : '2',
|
||||
position: (index % 6) + 1,
|
||||
}))
|
||||
|
||||
try {
|
||||
state.value = await api('/admin/positions/final', {
|
||||
method: 'PUT',
|
||||
admin: true,
|
||||
body: { slots },
|
||||
})
|
||||
showToast(t('messages.saved'))
|
||||
} catch (error) {
|
||||
showToast(`${t('messages.errorPrefix')}: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function autoGroupEvenly() {
|
||||
if (primaryGroups.value.length === 0) {
|
||||
showToast(t('messages.noGroupsConfigured'), 'error')
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
:score-proof-for="scoreProofFor"
|
||||
:open-score-proof-uploader="openScoreProofUploader"
|
||||
:open-proof-preview="openProofPreview"
|
||||
:show-proof-controls="false"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -195,6 +196,7 @@
|
||||
:request-score-advice="requestScoreAdvice"
|
||||
:highlight-stage="activeScoringStage"
|
||||
:highlight-group="activeScoringGroup"
|
||||
:show-proof-controls="false"
|
||||
@update:filter="$emit('update-score-filter', { stage: 'prelim_tiebreak', value: $event })"
|
||||
/>
|
||||
</template>
|
||||
@@ -208,6 +210,7 @@
|
||||
|
||||
<div class="panel-actions">
|
||||
<button class="btn btn-outline" @click="$emit('request-reset-stage', 'final')">{{ t('actions.resetScores') }}</button>
|
||||
<button class="btn btn-outline" @click="$emit('reset-final-group-by-seed')">{{ t('actions.resetFinalGroupBySeed') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="stage-filter-bar">
|
||||
@@ -259,6 +262,7 @@
|
||||
:score-proof-for="scoreProofFor"
|
||||
:open-score-proof-uploader="openScoreProofUploader"
|
||||
:open-proof-preview="openProofPreview"
|
||||
:show-proof-controls="false"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -314,6 +318,7 @@
|
||||
:request-score-advice="requestScoreAdvice"
|
||||
:highlight-stage="activeScoringStage"
|
||||
:highlight-group="activeScoringGroup"
|
||||
:show-proof-controls="false"
|
||||
@update:filter="$emit('update-score-filter', { stage: 'final_tiebreak', value: $event })"
|
||||
/>
|
||||
</template>
|
||||
@@ -439,6 +444,7 @@ defineEmits([
|
||||
'remove-player-image',
|
||||
'delete-player',
|
||||
'request-reset-stage',
|
||||
'reset-final-group-by-seed',
|
||||
'save-position-board',
|
||||
'update-score-filter',
|
||||
])
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
<td class="mono strong">{{ row.score }}</td>
|
||||
<td class="mono">{{ row.tieBreak }}</td>
|
||||
</tr>
|
||||
<tr v-if="preliminaryRows.length === 0"><td colspan="4" class="muted center">{{ t('labels.noPlayers') }}</td></tr>
|
||||
<tr v-if="preliminaryRowsForLive.length === 0"><td colspan="4" class="muted center">{{ t('labels.noPlayers') }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -374,6 +374,7 @@ const props = defineProps({
|
||||
positionBoards: { type: Object, default: () => ({}) },
|
||||
liveGroupCode: { type: String, default: '' },
|
||||
liveGroupMembers: { type: Array, required: true },
|
||||
prelimTie: { type: Object, default: () => ({ required: false, resolved: true, slots: 0, playerIds: [] }) },
|
||||
prelimTieRows: { type: Array, required: true },
|
||||
preliminaryRows: { type: Array, required: true },
|
||||
finalists: { type: Array, required: true },
|
||||
@@ -398,6 +399,7 @@ const props = defineProps({
|
||||
const finalistIds = computed(() => new Set((props.finalists || []).map((row) => row.playerId)))
|
||||
const prelimTieIds = computed(() => new Set((props.prelimTieRows || []).map((row) => row.playerId)))
|
||||
const finalTieIds = computed(() => new Set((props.finalTieRows || []).map((row) => row.playerId)))
|
||||
const prelimTieResolved = computed(() => Boolean(props.prelimTie?.resolved))
|
||||
const activeView = computed(() => props.liveSettings.activeView || 'group_live')
|
||||
const listPageIndex = ref(0)
|
||||
const tieGroupIndex = ref(0)
|
||||
@@ -418,8 +420,10 @@ const rotationPlayerCount = computed(() => {
|
||||
return Math.floor(raw)
|
||||
})
|
||||
|
||||
const preliminaryRowsForLive = computed(() => rankPreliminaryRowsWithTieBreak(props.preliminaryRows || []))
|
||||
|
||||
const listRowsForActiveView = computed(() => {
|
||||
if (activeView.value === 'prelim_overall') return props.preliminaryRows || []
|
||||
if (activeView.value === 'prelim_overall') return preliminaryRowsForLive.value
|
||||
if (activeView.value === 'final_overall') return props.finalOverallRows || []
|
||||
return []
|
||||
})
|
||||
@@ -432,7 +436,7 @@ const listPageCount = computed(() => {
|
||||
|
||||
const currentListPageDisplay = computed(() => `${listPageIndex.value + 1} / ${listPageCount.value}`)
|
||||
|
||||
const pagedPreliminaryRows = computed(() => paginateRows(props.preliminaryRows || []))
|
||||
const pagedPreliminaryRows = computed(() => paginateRows(preliminaryRowsForLive.value))
|
||||
const pagedFinalOverallRows = computed(() => paginateRows(props.finalOverallRows || []))
|
||||
const groupedPrelimTieRows = computed(() => groupTieRowsByPositionBoard('prelim_tiebreak', props.prelimTieRows || []))
|
||||
const groupedFinalTieRows = computed(() => groupTieRowsByPositionBoard('final_tiebreak', props.finalTieRows || []))
|
||||
@@ -508,7 +512,7 @@ function isFinalTiePlayer(playerId) {
|
||||
}
|
||||
|
||||
function prelimOverallHintLabel(row) {
|
||||
if (isPrelimTiePlayer(row.playerId)) return props.t('labels.tieBreak')
|
||||
if (!prelimTieResolved.value && isPrelimTiePlayer(row.playerId)) return props.t('labels.tieBreak')
|
||||
if (isFinalist(row.playerId)) return props.t('labels.finalist')
|
||||
return ''
|
||||
}
|
||||
@@ -538,6 +542,31 @@ function paginateRows(rows) {
|
||||
return rows.slice(start, start + rotationPlayerCount.value)
|
||||
}
|
||||
|
||||
function rankPreliminaryRowsWithTieBreak(rows) {
|
||||
const sorted = [...(rows || [])].sort((a, b) => {
|
||||
const scoreA = Number(a.score || 0)
|
||||
const scoreB = Number(b.score || 0)
|
||||
if (scoreA !== scoreB) return scoreB - scoreA
|
||||
const tieA = Number(a.tieBreak || 0)
|
||||
const tieB = Number(b.tieBreak || 0)
|
||||
if (tieA !== tieB) return tieB - tieA
|
||||
return Number(a.playerId || 0) - Number(b.playerId || 0)
|
||||
})
|
||||
|
||||
let currentRank = 1
|
||||
return sorted.map((row, index) => {
|
||||
if (index > 0) {
|
||||
const prev = sorted[index - 1]
|
||||
const isSameScore = Number(prev.score || 0) === Number(row.score || 0)
|
||||
const isSameTieBreak = Number(prev.tieBreak || 0) === Number(row.tieBreak || 0)
|
||||
if (!(isSameScore && isSameTieBreak)) {
|
||||
currentRank = index + 1
|
||||
}
|
||||
}
|
||||
return { ...row, rank: currentRank }
|
||||
})
|
||||
}
|
||||
|
||||
function restartListRotation() {
|
||||
stopListRotation()
|
||||
listPageIndex.value = 0
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
<div class="modal-card">
|
||||
<h3 class="modal-title">{{ t('actions.resetScores') }}</h3>
|
||||
<p class="modal-text">{{ t('messages.confirmReset') }}</p>
|
||||
<p class="modal-text subtle">{{ t('messages.resetProofPrompt') }}</p>
|
||||
<!-- <p class="modal-text subtle">{{ t('messages.resetProofPrompt') }}</p> -->
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-outline" @click="$emit('confirm', false)">{{ t('actions.resetOnlyScores') }}</button>
|
||||
<button class="btn btn-danger" @click="$emit('confirm', true)">{{ t('actions.resetScoresAndProofs') }}</button>
|
||||
<button class="btn btn-secondary" @click="$emit('cancel')">{{ t('actions.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
{{ round.label }}
|
||||
</th>
|
||||
<th>{{ t('table.total') }}</th>
|
||||
<th>{{ t('table.verification') }}</th>
|
||||
<th v-if="showProofControls">{{ t('table.verification') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -80,7 +80,7 @@
|
||||
/>
|
||||
</td>
|
||||
<td class="mono strong">{{ totalScore(row.playerId) }}</td>
|
||||
<td class="verify-round-cell">
|
||||
<td v-if="showProofControls" class="verify-round-cell">
|
||||
<div class="verify-round-list">
|
||||
<div v-for="round in roundStages" :key="'verify-' + round.stage + '-' + row.playerId" class="verify-round-item">
|
||||
<span class="verify-round-label">{{ round.label }}</span>
|
||||
@@ -151,7 +151,7 @@
|
||||
<strong class="mono">{{ totalScore(row.playerId) }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="mobile-verify-block">
|
||||
<div v-if="showProofControls" class="mobile-verify-block">
|
||||
<p class="mobile-verify-title">{{ t('table.verification') }}</p>
|
||||
<div class="verify-round-list mobile">
|
||||
<div v-for="round in roundStages" :key="'mobile-verify-' + round.stage + '-' + row.playerId" class="verify-round-item">
|
||||
@@ -195,6 +195,7 @@ const props = defineProps({
|
||||
showSeed: { type: Boolean, default: false },
|
||||
sectionByGroup: { type: Boolean, default: false },
|
||||
overflowLimit: { type: Number, default: 0 },
|
||||
showProofControls: { type: Boolean, default: true },
|
||||
playerImage: { type: Function, required: true },
|
||||
displayName: { type: Function, required: true },
|
||||
secondaryName: { type: Function, required: true },
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<th v-if="showGroup">{{ t('table.group') }}</th>
|
||||
<th v-if="showScoreBeforeInput">{{ t('table.score') }}</th>
|
||||
<th :class="{ 'stage-column-highlight': isHighlightedStage(stage, section) }">{{ inputLabel }}</th>
|
||||
<th>{{ t('table.verification') }}</th>
|
||||
<th v-if="showProofControls">{{ t('table.verification') }}</th>
|
||||
<th v-if="showRank">{{ t('table.rank') }}</th>
|
||||
<th v-if="showSeed">{{ t('table.seed') }}</th>
|
||||
</tr>
|
||||
@@ -65,23 +65,7 @@
|
||||
@keydown.enter.prevent="onScoreCommit(stage, row.playerId)"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div class="proof-actions">
|
||||
<button class="btn btn-outline btn-xs" @click="openScoreProofUploader(stage, row.playerId)">
|
||||
{{ hasScoreProof(stage, row.playerId) ? t('actions.replaceProof') : t('actions.uploadProof') }}
|
||||
</button>
|
||||
<button v-if="hasScoreProof(stage, row.playerId)" class="btn btn-danger btn-xs" @click="removeScoreProof(stage, row.playerId)">
|
||||
{{ t('actions.removeProof') }}
|
||||
</button>
|
||||
<img
|
||||
v-if="hasScoreProof(stage, row.playerId)"
|
||||
class="proof-thumb"
|
||||
:src="scoreProofFor(stage, row.playerId)"
|
||||
:alt="t('table.verification')"
|
||||
@click="openProofPreview(stage, row.playerId)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td v-if="showRank" class="mono rank">{{ row.rank }}</td>
|
||||
<td v-if="showSeed" class="mono">{{ row.seed }}</td>
|
||||
</tr>
|
||||
@@ -130,7 +114,7 @@
|
||||
@keydown.enter.prevent="onScoreCommit(stage, row.playerId)"
|
||||
/>
|
||||
|
||||
<div class="proof-actions mobile-proof-actions">
|
||||
<div v-if="showProofControls" class="proof-actions mobile-proof-actions">
|
||||
<button class="btn btn-outline btn-xs" @click="openScoreProofUploader(stage, row.playerId)">
|
||||
{{ hasScoreProof(stage, row.playerId) ? t('actions.replaceProof') : t('actions.uploadProof') }}
|
||||
</button>
|
||||
@@ -173,6 +157,7 @@ const props = defineProps({
|
||||
inputLabel: { type: String, required: true },
|
||||
sectionByGroup: { type: Boolean, default: false },
|
||||
tieBreakSectionMode: { type: Boolean, default: false },
|
||||
showProofControls: { type: Boolean, default: true },
|
||||
playerImage: { type: Function, required: true },
|
||||
displayName: { type: Function, required: true },
|
||||
secondaryName: { type: Function, required: true },
|
||||
|
||||
@@ -139,6 +139,7 @@ export const I18N = {
|
||||
removeImage: 'حذف الصورة',
|
||||
delete: 'حذف',
|
||||
resetScores: 'تصفير نتائج المرحلة',
|
||||
resetFinalGroupBySeed: 'إعادة مجموعات النهائي حسب التصنيف (1-6 / 7-12)',
|
||||
resetOnlyScores: 'تصفير النتائج فقط',
|
||||
resetScoresAndProofs: 'تصفير النتائج وحذف الإثبات',
|
||||
randomEvenGroups: 'توزيع عشوائي متوازن',
|
||||
@@ -340,6 +341,7 @@ export const I18N = {
|
||||
removeImage: 'Remove Image',
|
||||
delete: 'Delete',
|
||||
resetScores: 'Reset Stage Scores',
|
||||
resetFinalGroupBySeed: 'Reset Final Groups By Seed (1-6 / 7-12)',
|
||||
resetOnlyScores: 'Reset scores only',
|
||||
resetScoresAndProofs: 'Reset scores and proofs',
|
||||
randomEvenGroups: 'Random even grouping',
|
||||
|
||||
Reference in New Issue
Block a user