feat(fc3a): SubscriptionsView + ArtistSection + SourceRow + router

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 12:57:13 -04:00
parent ded5e5f642
commit 79d6d74637
4 changed files with 250 additions and 1 deletions
+144
View File
@@ -0,0 +1,144 @@
<template>
<v-container fluid class="py-6">
<div class="fc-subs__bar">
<v-btn color="accent" prepend-icon="mdi-plus" @click="openAddSource(null)">
Add subscription
</v-btn>
<v-btn variant="outlined" prepend-icon="mdi-account-plus" @click="showArtistDialog = true">
New artist
</v-btn>
<v-spacer />
<v-btn variant="text" size="small" @click="expandAll = !expandAll">
{{ expandAll ? 'Collapse all' : 'Expand all' }}
</v-btn>
</div>
<v-alert v-if="store.error" type="error" variant="tonal" closable class="mt-4">
{{ String(store.error) }}
</v-alert>
<div v-if="store.loading && groups.length === 0" class="fc-subs__loading">
<v-progress-circular indeterminate color="accent" size="36" />
</div>
<div v-else-if="groups.length === 0" class="fc-subs__empty">
<p>No subscriptions yet. Add your first artist.</p>
</div>
<div v-else>
<ArtistSection
v-for="g in groups" :key="g.artist.id"
:artist="g.artist" :sources="g.sources"
:open="isOpen(g.artist.id)"
@toggle="toggleSection(g.artist.id)"
@edit="openEditSource"
@remove="removeSource"
@toggle-source="toggleSourceEnabled"
@add-source="openAddSource"
/>
</div>
<SourceFormDialog
v-model="showSourceDialog"
:source="editingSource"
:initial-artist="editingArtist"
@saved="onSourceSaved"
/>
<ArtistCreateDialog v-model="showArtistDialog" @created="onArtistCreated" />
</v-container>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useSourcesStore } from '../stores/sources.js'
import ArtistSection from '../components/subscriptions/ArtistSection.vue'
import SourceFormDialog from '../components/subscriptions/SourceFormDialog.vue'
import ArtistCreateDialog from '../components/subscriptions/ArtistCreateDialog.vue'
const route = useRoute()
const store = useSourcesStore()
const artistFilter = computed(() => {
const raw = route.query.artist_id
return raw == null ? null : Number(raw)
})
const expandAll = ref(false)
const openSections = ref(new Set())
const showSourceDialog = ref(false)
const editingSource = ref(null)
const editingArtist = ref(null)
const showArtistDialog = ref(false)
async function refresh() {
await store.loadAll()
await store.loadPlatforms()
if (artistFilter.value != null) {
openSections.value = new Set([artistFilter.value])
expandAll.value = false
}
}
onMounted(refresh)
watch(() => route.query.artist_id, refresh)
const groups = computed(() => {
const all = store.sourcesByArtistGrouped()
if (artistFilter.value == null) return all
return all.filter(g => g.artist.id === artistFilter.value)
})
function isOpen(artistId) {
return expandAll.value || openSections.value.has(artistId)
}
function toggleSection(artistId) {
if (openSections.value.has(artistId)) openSections.value.delete(artistId)
else openSections.value.add(artistId)
}
function openAddSource(artist) {
editingSource.value = null
editingArtist.value = artist
showSourceDialog.value = true
}
function openEditSource(source) {
editingSource.value = source
editingArtist.value = { id: source.artist_id, name: source.artist_name, slug: source.artist_slug }
showSourceDialog.value = true
}
async function removeSource(source) {
await store.remove(source.id, source.artist_id)
await refresh()
}
async function toggleSourceEnabled({ source, enabled }) {
await store.update(source.id, { enabled }, source.artist_id)
await refresh()
}
async function onSourceSaved() {
showSourceDialog.value = false
await refresh()
}
function onArtistCreated(artist) {
showArtistDialog.value = false
// Move into Add Source for the new artist immediately.
openAddSource(artist)
}
</script>
<style scoped>
.fc-subs__bar {
display: flex; gap: 0.75rem; align-items: center;
padding-bottom: 1rem;
}
.fc-subs__loading, .fc-subs__empty {
display: flex; justify-content: center; padding: 2rem;
color: rgb(var(--v-theme-on-surface-variant));
}
</style>