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:
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user