diff --git a/apps/admin/src/components/LeagueRowActions.vue b/apps/admin/src/components/LeagueRowActions.vue index 40f87f1..f1f24bd 100644 --- a/apps/admin/src/components/LeagueRowActions.vue +++ b/apps/admin/src/components/LeagueRowActions.vue @@ -2,6 +2,7 @@ export interface LeagueRowView { id: string; isPublished: boolean; + isOutrightSettled?: boolean; isPublishing: boolean; labels: { edit: string; @@ -30,7 +31,12 @@ defineEmits<{ {{ row.labels.edit }} - + {{ row.labels.createFixture }} (); -const { t, locale, localeTag } = useAdminLocale(); +const { t, locale } = useAdminLocale(); const auth = useAuthStore(); const visible = computed({ @@ -68,17 +69,6 @@ function depositMethodLabel(row: WalletTxRow) { return walletDepositMethodLabel(row, t); } -function formatTime(v: string) { - return new Date(v).toLocaleString(localeTag.value, { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - }); -} - function dateParams() { if (!dateRange.value?.length) return {}; const [from, to] = dateRange.value; @@ -172,8 +162,12 @@ watch( - - + + @@ -181,45 +175,27 @@ watch( - + - + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/apps/admin/src/views/Matches.vue b/apps/admin/src/views/Matches.vue index f12c21b..ead8952 100644 --- a/apps/admin/src/views/Matches.vue +++ b/apps/admin/src/views/Matches.vue @@ -3,6 +3,7 @@ import { ref, computed, watch, onBeforeUnmount, onDeactivated } from 'vue'; defineOptions({ name: 'AdminMatches' }); import { useStaleListLifecycle } from '../composables/useStaleList'; +import { consumeAdminListStale } from '../utils/adminListStale'; import { useRoute, useRouter } from 'vue-router'; import { useAdminLocale } from '../composables/useAdminLocale'; import { resolveFormError } from '../i18n/form-validation'; @@ -38,6 +39,7 @@ const isMatchChildRoute = computed(() => interface LeagueTableRow extends Record { id: string; isPublished: boolean; + isOutrightSettled: boolean; isPublishing: boolean; labels: { edit: string; @@ -50,7 +52,7 @@ interface LeagueTableRow extends Record { displayNameZh: string; displayNameEn: string; displayStatusLabel: string; - displayStatusTagType: 'success' | 'info'; + displayStatusTagType: 'success' | 'info' | 'warning'; displayMatchCount: number; displayBetCount: number; displayBetCountActive: boolean; @@ -121,6 +123,7 @@ function mapLeagueRows(items: unknown[], publishingId = ''): LeagueTableRow[] { const r = rowOf(item); const id = String(r.id ?? ''); const published = Boolean(r.isPublished); + const outrightSettled = Boolean(r.isOutrightSettled); const stats = r.betStats as | { betCount?: number; totalStake?: string; pendingCount?: number } | undefined; @@ -129,13 +132,18 @@ function mapLeagueRows(items: unknown[], publishingId = ''): LeagueTableRow[] { ...r, id, isPublished: published, + isOutrightSettled: outrightSettled, isPublishing: publishingId === id, labels, displaySeq: start + index + 1, displayNameZh: String(r.leagueZh ?? '').trim() || '—', displayNameEn: String(r.leagueEn ?? '').trim() || '—', - displayStatusLabel: published ? t('league.status.PUBLISHED') : t('league.status.UNPUBLISHED'), - displayStatusTagType: published ? 'success' : 'info', + displayStatusLabel: outrightSettled + ? t('league.status.OUTRIGHT_SETTLED') + : published + ? t('league.status.PUBLISHED') + : t('league.status.UNPUBLISHED'), + displayStatusTagType: outrightSettled ? 'warning' : published ? 'success' : 'info', displayMatchCount: Number(r.matchCount ?? 0), displayBetCount: betCount, displayBetCountActive: betCount > 0, @@ -163,6 +171,7 @@ function persistListUiState() { type LoadOptions = { restore?: boolean }; async function load(options: LoadOptions = {}) { + consumeAdminListStale(); const saved = options.restore ? readMatchesListUiState() : null; if (saved) { page.value = saved.page; @@ -189,6 +198,17 @@ function onSearch() { void runLoad(true); } +const MATCH_CHILD_ROUTE = /^\/matches\/leagues\/[^/]+/; + +watch( + () => route.path, + (path, prevPath) => { + if (prevPath && MATCH_CHILD_ROUTE.test(prevPath) && path === '/matches') { + void load(); + } + }, +); + async function initialLoad() { if (isMatchChildRoute.value) return; const qStatus = route.query.status; diff --git a/apps/admin/src/views/MatchesOutrights.vue b/apps/admin/src/views/MatchesOutrights.vue index fe5011b..81b9d74 100644 --- a/apps/admin/src/views/MatchesOutrights.vue +++ b/apps/admin/src/views/MatchesOutrights.vue @@ -1,5 +1,6 @@ + + + @@ -270,4 +294,7 @@ function outrightTeamCount(row: unknown) { color: var(--text-muted); font-size: 13px; } +.status-dash { + color: var(--text-muted); +} diff --git a/apps/admin/src/views/Settlement.vue b/apps/admin/src/views/Settlement.vue index d2dbe0a..92bf6ea 100644 --- a/apps/admin/src/views/Settlement.vue +++ b/apps/admin/src/views/Settlement.vue @@ -23,7 +23,7 @@ import { betTypeLabel, betResultLabel, } from '../utils/bet-labels'; -import { adminSelectionLabel } from '../utils/adminSelectionLabel'; +import { markAdminListStale } from '../utils/adminListStale'; import type { AdminMatchDetail } from './match-form'; import AdminSubNav from '../components/AdminSubNav.vue'; @@ -658,6 +658,7 @@ async function confirmResettle() { resettleDialogVisible.value = false; await loadMatch(); void loadSettlementHistory(); + markAdminListStale(); } catch (e: unknown) { ElMessage.error(settlementApiError(e, t('settlement.preview_failed'))); } finally { @@ -726,6 +727,7 @@ async function previewSettlement() { previewPageSize.value = itemsPage.pageSize; previewDialogVisible.value = true; await loadMatch(); + markAdminListStale(); } catch (e: unknown) { ElMessage.error(settlementApiError(e, t('settlement.preview_failed'))); } finally { @@ -754,6 +756,7 @@ async function confirm() { previewDialogVisible.value = false; await loadMatch(); void loadSettlementHistory(); + markAdminListStale(); } catch (e: unknown) { ElMessage.error(settlementApiError(e, t('settlement.preview_failed'))); } finally { diff --git a/apps/admin/src/views/matches/LeagueMatchesPage.vue b/apps/admin/src/views/matches/LeagueMatchesPage.vue index f2eb289..9db1296 100644 --- a/apps/admin/src/views/matches/LeagueMatchesPage.vue +++ b/apps/admin/src/views/matches/LeagueMatchesPage.vue @@ -34,9 +34,15 @@ const leagueTitle = computed(() => { const panelRef = ref<{ reload: () => void } | null>(null); const createVisible = ref(false); const createLoading = ref(false); +const isOutrightSettled = ref(false); const form = ref(emptyMatchForm()); +function onLeagueMeta(meta: { isOutrightSettled: boolean }) { + isOutrightSettled.value = meta.isOutrightSettled; +} + function openCreateFixture() { + if (isOutrightSettled.value) return; form.value = emptyMatchForm(); form.value.leagueId = leagueId.value; createVisible.value = true; @@ -85,9 +91,10 @@ watch(leagueId, () => { :subtitle="t('match.league_fixtures_subtitle')" > @@ -97,6 +104,7 @@ watch(leagueId, () => { :league-id="leagueId" :filter-status="filterStatus" :keyword="filterKeyword" + @league-meta="onLeagueMeta" /> @@ -254,6 +262,12 @@ watch(leagueId, () => { font-weight: 700; } +.settled-hint { + font-size: 13px; + color: var(--warning-text); + font-weight: 600; +} + @media (max-width: 760px) { .teams-row { grid-template-columns: 1fr; diff --git a/apps/admin/src/views/matches/LeagueMatchesPanel.vue b/apps/admin/src/views/matches/LeagueMatchesPanel.vue index 4eec792..d5e2e4e 100644 --- a/apps/admin/src/views/matches/LeagueMatchesPanel.vue +++ b/apps/admin/src/views/matches/LeagueMatchesPanel.vue @@ -1,5 +1,6 @@