Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions components/hub/NotificationsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ export default {
});
}
}
// Synthetic notifications have no backing row to delete.
if (notification.__synthetic) {
return;
}
await this.deleteNotification(notification.id);
},
async dismissNotification(id: string) {
Expand Down
22 changes: 17 additions & 5 deletions components/player/PlayerIntroDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,18 @@ const props = defineProps<{
source?: string | null;
limit?: number | null;
since?: string | null;
until?: string | null;
}>();

const { t, te } = useI18n();

// A null/absent limit means "no match cap" (a date-range / season filter is
// driving the window); only fall back to the recent-30 view when there is no
// range at all. Bounded so an all-time query can't be unbounded.
const effectiveLimit = computed(() =>
props.limit != null ? props.limit : props.since ? 1000 : 30,
);

function statTitle(key: string): string {
return te(`stat_glossary.${key}.label`)
? t(`stat_glossary.${key}.label`)
Expand Down Expand Up @@ -92,8 +100,11 @@ function buildMatchesWhere() {
},
};
}
if (props.since) {
where.started_at = { _gte: props.since };
if (props.since || props.until) {
where.started_at = {
...(props.since ? { _gte: props.since } : {}),
...(props.until ? { _lte: props.until } : {}),
};
}
return where;
}
Expand Down Expand Up @@ -185,7 +196,7 @@ async function load() {
variables: {
steamId: props.steamId,
matchesWhere: buildMatchesWhere(),
limit: props.limit ?? 30,
limit: effectiveLimit.value,
statsLimit: 200,
hltvLimit: 600,
},
Expand Down Expand Up @@ -218,6 +229,7 @@ watch(
props.matchType,
props.limit,
props.since,
props.until,
],
load,
{ immediate: true },
Expand Down Expand Up @@ -465,7 +477,7 @@ const {
(steamId) => ({
steamId,
matchesWhere: buildMatchesWhere(),
limit: props.limit ?? 30,
limit: effectiveLimit.value,
statsLimit: 200,
hltvLimit: 600,
}),
Expand All @@ -474,7 +486,7 @@ const {
rawStats: (data?.playerIntroStats ?? []) as RawStats[],
hltvRows: (data?.playerIntroHltv ?? []) as any[],
}),
() => [props.source, props.matchType, props.limit, props.since],
() => [props.source, props.matchType, props.limit, props.since, props.until],
);

const comparePoints = computed<MatchPoint[]>(() => {
Expand Down
60 changes: 60 additions & 0 deletions components/seasons/SeasonRebuildProgress.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useSeasonBackfill } from "~/composables/useSeasonBackfill";

const props = defineProps<{ seasonId: string }>();

const { t } = useI18n();
const backfill = useSeasonBackfill();

const active = computed(
() =>
backfill.running.value &&
backfill.status.value?.season_id === props.seasonId,
);
</script>

<template>
<Transition
enter-active-class="transition-all duration-300 ease-out motion-reduce:!duration-0"
enter-from-class="opacity-0 -translate-y-1"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition-all duration-200 ease-in motion-reduce:!duration-0"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 -translate-y-1"
>
<div v-if="active" class="mt-2.5 space-y-1">
<div class="flex items-center justify-between gap-2">
<span
class="inline-flex items-center gap-1.5 font-mono text-[0.55rem] uppercase tracking-[0.16em] text-[hsl(var(--tac-amber))]"
>
<span
class="inline-block h-[6px] w-[6px] rotate-45 animate-pulse bg-[hsl(var(--tac-amber))]"
></span>
{{ t("pages.seasons.backfill_running") }}
<span class="text-muted-foreground"
>· {{ backfill.status.value?.completed || 0 }}/{{
backfill.status.value?.total || 0
}}
{{ t("pages.seasons.matches_label") }}</span
>
</span>
<button
type="button"
class="font-mono text-[0.55rem] uppercase tracking-[0.14em] text-muted-foreground transition-colors hover:text-destructive disabled:opacity-40"
:disabled="backfill.canceling.value"
@click="backfill.cancelBackfill()"
>
{{ t("common.cancel") }}
</button>
</div>
<div class="relative h-1 w-full overflow-hidden rounded-full bg-muted/40">
<div
class="absolute inset-y-0 left-0 rounded-full bg-[hsl(var(--tac-amber))] transition-[width] duration-500"
:style="{ width: (backfill.progress.value || 0) + '%' }"
></div>
</div>
</div>
</Transition>
</template>
5 changes: 4 additions & 1 deletion i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1604,7 +1604,7 @@
"refresh_dialog_title": "Reindex the search index?",
"refresh_dialog_description": "This will re-sync every player into the Typesense search index in batches. It runs in the background and you can watch its progress here.",
"recompute_elo_title": "Recompute Player ELO",
"recompute_elo_description": "Wipe and rebuild every player's ELO by replaying all finished matches in order, in batches. Use this after an ELO formula change or if ratings look wrong. Search results update automatically as ELO is recalculated.",
"recompute_elo_description": "Wipe and rebuild every player's ELO by replaying all finished matches in order, in batches. When seasons are enabled this also rebuilds each season's ELO from the matches that fall within it. Use this after an ELO formula change or if ratings look wrong. Search results update automatically as ELO is recalculated.",
"recompute_elo_button": "Recompute ELO",
"recomputing_elo": "Recomputing...",
"recompute_elo_queued": "ELO recompute started",
Expand Down Expand Up @@ -2203,6 +2203,9 @@
"upcoming_title": "Upcoming Seasons",
"past_title": "Past Seasons",
"rebuild": "Rebuild",
"rebuild_elo": "Rebuild ELO",
"rebuild_required": "Rebuild Required",
"needs_rebuild_notice": "This season's ELO is out of date and its standings are incorrect. Rebuild to recompute ELO and stats from its matches.",
"delete": "Delete",
"deleted": "Season deleted",
"delete_confirm_title": "Delete this season?",
Expand Down
6 changes: 6 additions & 0 deletions layouts/components/AppNotifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ export default {
}
}

// Synthetic notifications have no backing row — they clear on their own
// when the underlying state changes (e.g. seasons.needs_rebuild flips).
if (notification.__synthetic) {
return;
}

await this.deleteNotification(notification.id);
},
async dismissNotification(id: string) {
Expand Down
9 changes: 8 additions & 1 deletion layouts/components/LeftNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ function onLeftNavTouchEnd(e: TouchEvent) {
</SidebarMenuItem>

<SidebarMenuItem
v-if="seasonsEnabled"
v-if="isAdmin && seasonsEnabled"
:tooltip="$t('layouts.app_nav.administration.seasons')"
>
<SidebarMenuButton
Expand All @@ -719,6 +719,10 @@ function onLeftNavTouchEnd(e: TouchEvent) {
>
<Leaf />
{{ $t("layouts.app_nav.administration.seasons") }}
<AlertTriangle
v-if="seasonsRebuildCount > 0"
class="ml-auto h-3.5 w-3.5 shrink-0 text-[hsl(var(--tac-amber))]"
/>
</NuxtLink>
</SidebarMenuButton>
</SidebarMenuItem>
Expand Down Expand Up @@ -1090,6 +1094,9 @@ export default {
seasonsEnabled() {
return useApplicationSettingsStore().seasonsEnabled;
},
seasonsRebuildCount() {
return useNotificationStore().seasonRebuildCount;
},
newsLabel() {
return useApplicationSettingsStore().newsLabel;
},
Expand Down
1 change: 1 addition & 0 deletions pages/players/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,7 @@ const playerHeroTeamChipDotClasses =
:source="effectiveSource"
:limit="statsMatchLimit"
:since="sinceTimestamp"
:until="untilTimestamp"
/>
</PageTransition>
</TabsContent>
Expand Down
Loading