Link media to people (person page + media page)

The Media model already carried person_id/event_id/source_id and the upload
route already accepted person_id — this surfaces it in the UI:

- Person page: a Media card lists media linked to that person, uploads new
  files already linked ("Upload & link"), links existing unlinked media, and
  unlinks.
- Media page: each item gets a person picker to link/unlink.

Frontend only — no migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 11:19:25 -04:00
parent 5106538934
commit d27cc5dddc
2 changed files with 142 additions and 2 deletions
+29
View File
@@ -9,6 +9,9 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
type Media = components["schemas"]["MediaRead"];
type Person = components["schemas"]["PersonRead"];
const fieldCls = "h-8 w-full rounded-md border border-[var(--border)] bg-[var(--surface)] px-2 text-xs";
function humanSize(bytes: number) {
if (bytes < 1024) return `${bytes} B`;
@@ -22,6 +25,7 @@ export default function MediaPage() {
const treeId = params.id;
const [items, setItems] = useState<Media[]>([]);
const [people, setPeople] = useState<Person[]>([]);
const [ready, setReady] = useState(false);
const [uploading, setUploading] = useState(false);
const fileRef = useRef<HTMLInputElement>(null);
@@ -34,10 +38,22 @@ export default function MediaPage() {
router.push("/login");
return;
}
const ppl = await api.GET("/api/v1/trees/{tree_id}/persons", {
params: { path: { tree_id: treeId } },
});
setItems(data ?? []);
setPeople(ppl.data ?? []);
setReady(true);
}, [router, treeId]);
async function linkPerson(mediaId: string, personId: string) {
await api.PATCH("/api/v1/trees/{tree_id}/media/{media_id}", {
params: { path: { tree_id: treeId, media_id: mediaId } },
body: { person_id: personId || null },
});
load();
}
useEffect(() => {
load();
}, [load]);
@@ -124,6 +140,19 @@ export default function MediaPage() {
×
</button>
</div>
<select
className={`${fieldCls} mt-2`}
value={m.person_id ?? ""}
onChange={(e) => linkPerson(m.id, e.target.value)}
title="Link to a person"
>
<option value=""> link to person </option>
{people.map((p) => (
<option key={p.id} value={p.id}>
{p.primary_name ?? "Unnamed"}
</option>
))}
</select>
</CardContent>
</Card>
))}