feat(artist-view): ArtistManagementTab — Overview chips (Subscription badge + subscription count) + Frequent tags + Activity sparkline + Subscriptions table + Danger zone. 'View posts' chip and 'Credential health · FC-3b' placeholder chip dropped; 'Sources' section renamed to 'Subscriptions'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="fc-artist-mgmt">
|
||||
<section class="fc-artist-mgmt__sec">
|
||||
<h2 class="fc-h2">Overview</h2>
|
||||
<div class="fc-artist-mgmt__chips">
|
||||
<v-chip
|
||||
size="small"
|
||||
:variant="overview.is_subscription ? 'flat' : 'outlined'"
|
||||
:color="overview.is_subscription ? 'accent' : undefined"
|
||||
prepend-icon="mdi-rss"
|
||||
>{{ overview.is_subscription ? 'Subscription' : 'One-off' }}</v-chip>
|
||||
<v-chip
|
||||
size="small" variant="outlined" prepend-icon="mdi-link-variant"
|
||||
:to="`/subscriptions?artist_id=${overview.id}`"
|
||||
>{{ overview.sources.length }} subscription{{ overview.sources.length === 1 ? '' : 's' }}</v-chip>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="overview.cooccurring_tags.length" class="fc-artist-mgmt__sec">
|
||||
<h2 class="fc-h2">Frequent tags</h2>
|
||||
<div class="fc-artist-mgmt__tags">
|
||||
<v-chip
|
||||
v-for="t in overview.cooccurring_tags" :key="t.id"
|
||||
size="small" @click="openTag(t.id)"
|
||||
>{{ t.name }} <span class="fc-artist-mgmt__tagc">{{ t.count }}</span></v-chip>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="overview.activity.length" class="fc-artist-mgmt__sec">
|
||||
<h2 class="fc-h2">Activity</h2>
|
||||
<svg
|
||||
class="fc-artist-mgmt__spark" :viewBox="`0 0 ${sparkW} ${sparkH}`"
|
||||
preserveAspectRatio="none" role="img" aria-label="posts over time"
|
||||
>
|
||||
<polyline :points="sparkPoints" fill="none"
|
||||
stroke="rgb(var(--v-theme-accent))" stroke-width="2" />
|
||||
</svg>
|
||||
</section>
|
||||
|
||||
<section v-if="overview.sources.length" class="fc-artist-mgmt__sec">
|
||||
<div class="fc-artist-mgmt__sec-head">
|
||||
<h2 class="fc-h2">Subscriptions</h2>
|
||||
<RouterLink
|
||||
:to="`/subscriptions?artist_id=${overview.id}`"
|
||||
class="fc-artist-mgmt__manage"
|
||||
>Manage subscriptions →</RouterLink>
|
||||
</div>
|
||||
<v-table density="compact">
|
||||
<thead>
|
||||
<tr><th>Platform</th><th>URL</th><th class="text-right">Images</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="s in overview.sources" :key="s.id">
|
||||
<td>{{ s.platform }}</td>
|
||||
<td class="fc-artist-mgmt__url">{{ s.url }}</td>
|
||||
<td class="text-right">{{ s.image_count }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</section>
|
||||
|
||||
<section class="fc-artist-mgmt__sec">
|
||||
<h2 class="fc-h2">Danger zone</h2>
|
||||
<ArtistDangerZone
|
||||
:slug="overview.slug"
|
||||
:artist-id="overview.id"
|
||||
:artist-name="overview.name"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRouter, RouterLink } from 'vue-router'
|
||||
|
||||
import ArtistDangerZone from './ArtistDangerZone.vue'
|
||||
|
||||
const props = defineProps({
|
||||
overview: { type: Object, required: true },
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const sparkW = 600
|
||||
const sparkH = 80
|
||||
const sparkPoints = computed(() => {
|
||||
const a = props.overview.activity ?? []
|
||||
if (a.length === 0) return ''
|
||||
const max = Math.max(...a.map(p => p.count), 1)
|
||||
const stepX = a.length > 1 ? sparkW / (a.length - 1) : 0
|
||||
return a.map((p, i) => {
|
||||
const x = i * stepX
|
||||
const y = sparkH - (p.count / max) * (sparkH - 4) - 2
|
||||
return `${x.toFixed(1)},${y.toFixed(1)}`
|
||||
}).join(' ')
|
||||
})
|
||||
|
||||
function openTag (tagId) {
|
||||
router.push({ name: 'gallery', query: { tag_id: tagId } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-artist-mgmt { padding-top: 1rem; }
|
||||
.fc-h2 {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-size: 20px; font-weight: 500; margin-bottom: 8px;
|
||||
}
|
||||
.fc-artist-mgmt__sec { margin-bottom: 28px; }
|
||||
.fc-artist-mgmt__chips { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.fc-artist-mgmt__tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.fc-artist-mgmt__tagc {
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
margin-left: 4px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.fc-artist-mgmt__spark { width: 100%; height: 80px; }
|
||||
.fc-artist-mgmt__url {
|
||||
max-width: 380px; overflow: hidden; text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fc-artist-mgmt__sec-head {
|
||||
display: flex; align-items: baseline; justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.fc-artist-mgmt__manage {
|
||||
font-size: 0.85rem;
|
||||
color: rgb(var(--v-theme-accent));
|
||||
text-decoration: none;
|
||||
}
|
||||
.fc-artist-mgmt__manage:hover { text-decoration: underline; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user