Files
minstrel/web/src/lib/components/TabStrip.svelte
T
bvandeusenandClaude Opus 5.5 ace7e22e6a
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m10s
release / android (push) Successful in 5m16s
release / Build signed APK (releases and dev) (push) Successful in 5m27s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
feat: Admin is a left rail with how many wait on you in each queue, and the Overview opens with a verdict, the queues that need you and since when, then the clear ones on one line (M503 #5605)
- GET /api/admin/summary: every admin queue (requests, reported tracks,
  playback errors, duplicates, suspect sources, missing files) with its
  count and the age of its oldest item, in one query.
- The admin layout drops the 11-tab strip for SectionRail, as Settings and
  the Collection have, with each queue's count as its badge; the counts
  refresh on each admin navigation and every 30s.
- The Overview leads with "Nothing needs you." or "N things need you.",
  lists each waiting queue with "oldest 3d ago" and a link to it, counts a
  failed library scan as a problem, and names the empty queues on one line.
  The request and quarantine previews and the stat tiles are gone; those
  queues are worked on their own pages. A failed summary says so with Try
  again instead of reading as all-clear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:58:13 -04:00

48 lines
1.4 KiB
Svelte

<script lang="ts" generics="Id extends string">
// In-page tabs: one strip of buttons switching what a single page shows
// (#5416). The same look as DiscoverTabs, which is route
// navigation and so stays links; this is for state inside one page, such as
// Liked's Artists | Albums | Tracks or a queue's status filter.
type Item = {
id: Id;
label: string;
/** Shown as a pill after the label. Omit for no pill. */
count?: number;
};
let {
items,
value = $bindable(),
ariaLabel
}: {
items: Item[];
value: Id;
ariaLabel: string;
} = $props();
</script>
<div role="tablist" aria-label={ariaLabel} class="flex gap-2 border-b border-border">
{#each items as item (item.id)}
{@const active = value === item.id}
<button
type="button"
role="tab"
aria-selected={active}
class="border-b-2 px-3 py-2 text-sm transition-colors {active
? 'border-accent text-text-primary'
: 'border-transparent text-text-secondary hover:text-text-primary'}"
onclick={() => (value = item.id)}
>
{item.label}
{#if item.count !== undefined}
<span
class="ml-1.5 inline-flex items-center rounded-full bg-accent-tint px-2 py-0.5 text-xs text-accent-fg"
data-testid="tab-count"
>
{item.count}
</span>
{/if}
</button>
{/each}
</div>