Redesign Library view to match mockup spec

- Replace band-name header + tab structure (By Date / Search) with a
  unified Library view: title, inline search input, filter pills
  (All / instrument / Commented), and date-group headers
- Session rows now use the recording-row card style (play circle,
  mono filename, recording count)
- Move mini waveform bars from session list to individual recording
  rows in SessionPage, where they correspond to a single track
- Fix Invalid Date by appending T12:00:00 when parsing date-only
  ISO strings in both BandPage and SessionPage
- Update tests: drop tab assertions (TC-07), add Library heading
  (TC-08) and filter pill (TC-09) checks, update upload button label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mistral Vibe
2026-04-06 18:37:49 +02:00
parent aa889579a0
commit b09094658c
3 changed files with 332 additions and 519 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useMemo } from "react";
import { useParams, Link } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "../api/client";
@@ -24,10 +24,29 @@ interface SessionDetail {
}
function formatDate(iso: string): string {
const d = new Date(iso);
const d = new Date(iso + "T12:00:00");
return d.toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
}
function computeWaveBars(seed: string): number[] {
let s = seed.split("").reduce((acc, c) => acc + c.charCodeAt(0), 31337);
return Array.from({ length: 14 }, () => {
s = ((s * 1664525 + 1013904223) & 0xffffffff) >>> 0;
return Math.max(15, Math.floor((s / 0xffffffff) * 100));
});
}
function MiniWaveBars({ seed }: { seed: string }) {
const bars = useMemo(() => computeWaveBars(seed), [seed]);
return (
<div style={{ display: "flex", alignItems: "flex-end", gap: "1.5px", height: 18, width: 34, flexShrink: 0 }}>
{bars.map((h, i) => (
<div key={i} style={{ width: 2, background: "rgba(255,255,255,0.11)", borderRadius: 1, height: `${h}%` }} />
))}
</div>
);
}
export function SessionPage() {
const { bandId, sessionId } = useParams<{ bandId: string; sessionId: string }>();
const qc = useQueryClient();
@@ -165,9 +184,12 @@ export function SessionPage() {
)}
</div>
</div>
<div style={{ color: "var(--text-muted)", fontSize: 12, whiteSpace: "nowrap" }}>
<span style={{ background: "var(--bg-subtle)", borderRadius: 4, padding: "2px 6px", marginRight: 8, fontFamily: "monospace", fontSize: 10 }}>{song.status}</span>
{song.version_count} version{song.version_count !== 1 ? "s" : ""}
<div style={{ display: "flex", alignItems: "center", gap: 10, flexShrink: 0 }}>
<MiniWaveBars seed={song.id} />
<div style={{ color: "var(--text-muted)", fontSize: 12, whiteSpace: "nowrap" }}>
<span style={{ background: "var(--bg-subtle)", borderRadius: 4, padding: "2px 6px", marginRight: 8, fontFamily: "monospace", fontSize: 10 }}>{song.status}</span>
{song.version_count} version{song.version_count !== 1 ? "s" : ""}
</div>
</div>
</Link>
))}