dc7fa6eae2
Daily-use reorder of the Subscriptions tab: needs-attention strip first (FailingSourcesCard moves up from below the Downloads fold — a broken subscription was invisible unless you went looking), then a new Recent arrivals card (real downloads only, no-change scans filtered out, artist links), then the source list. Both cards render nothing when there's nothing to say. Retry logic moves into the downloads store (retrySource / retryAllFailing) so the needs-attention card and the Downloads maintenance menu share one implementation — single-retry forces past cooldown, bulk keeps cooldown enforcement, same tally shape. The card's Logs button deep-links into the Downloads tab pre-filtered (?source_id now watched, not just read on mount). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
81 lines
2.4 KiB
Vue
81 lines
2.4 KiB
Vue
<template>
|
|
<v-card v-if="arrivals.length" class="mb-4">
|
|
<CardHeading icon="mdi-new-box" title="Recent arrivals">
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text" size="small" rounded="pill"
|
|
to="/subscriptions?tab=downloads"
|
|
>
|
|
All downloads
|
|
<v-icon end size="small">mdi-arrow-right</v-icon>
|
|
</v-btn>
|
|
</CardHeading>
|
|
<v-card-text class="pt-0">
|
|
<div
|
|
v-for="ev in arrivals" :key="ev.id"
|
|
class="fc-arrival"
|
|
>
|
|
<router-link
|
|
v-if="ev.artist_slug"
|
|
:to="`/artist/${ev.artist_slug}`"
|
|
class="fc-arrival__artist"
|
|
>{{ ev.artist_name || ev.artist_slug }}</router-link>
|
|
<span v-else class="fc-arrival__artist">{{ ev.artist_name || '—' }}</span>
|
|
<span class="fc-arrival__meta">
|
|
{{ ev.platform }} ·
|
|
{{ ev.files_count ? `${ev.files_count} file(s)` : 'no new files' }}
|
|
· {{ formatRelative(ev.started_at) }}
|
|
</span>
|
|
</div>
|
|
</v-card-text>
|
|
</v-card>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
import CardHeading from '../common/CardHeading.vue'
|
|
import { formatRelative } from '../../utils/date.js'
|
|
import { useApi } from '../../composables/useApi.js'
|
|
|
|
// "What came in?" — the other half of the landing tab's daily answer
|
|
// (2026-07-02). Own fetch, own state: the downloads store's event list is
|
|
// the Downloads tab's FILTERED feed; borrowing it would couple this card to
|
|
// whatever filter the operator left that tab on.
|
|
const api = useApi()
|
|
const arrivals = ref([])
|
|
let pollId = null
|
|
|
|
async function load() {
|
|
try {
|
|
const events = await api.get('/api/downloads', {
|
|
params: { status: 'ok', limit: 25 },
|
|
})
|
|
// Real arrivals only — scheduled scans that found nothing are noise here.
|
|
arrivals.value = events.filter((e) => (e.files_count || 0) > 0).slice(0, 6)
|
|
} catch { /* non-fatal — the card just hides */ }
|
|
}
|
|
|
|
onMounted(() => {
|
|
load()
|
|
pollId = setInterval(() => { if (!document.hidden) load() }, 60000)
|
|
})
|
|
onUnmounted(() => { if (pollId) clearInterval(pollId) })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-arrival {
|
|
display: flex; align-items: baseline; gap: 10px;
|
|
padding: 4px 0;
|
|
}
|
|
.fc-arrival__artist {
|
|
font-weight: 600; text-decoration: none;
|
|
color: rgb(var(--v-theme-accent));
|
|
}
|
|
.fc-arrival__artist:hover { text-decoration: underline; }
|
|
.fc-arrival__meta {
|
|
font-size: 12px;
|
|
color: rgb(var(--v-theme-on-surface-variant));
|
|
}
|
|
</style>
|