feat(web): vocabularies two-pane screen (list/create + terms/add) + nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 09:22:38 +02:00
parent e8d173a18f
commit ac30eadbb2
9 changed files with 270 additions and 2 deletions
+67
View File
@@ -0,0 +1,67 @@
import { useState, type FormEvent } from "react";
import { NavLink } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useVocabularies, useCreateVocabulary } from "../api/queries";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function VocabularyList() {
const { t } = useTranslation();
const { data, isLoading, isError } = useVocabularies();
const create = useCreateVocabulary();
const [key, setKey] = useState("");
const onCreate = (event: FormEvent) => {
event.preventDefault();
if (!key.trim()) return;
create.mutate({ key: key.trim() }, { onSuccess: () => setKey("") });
};
return (
<div className="flex h-full flex-col">
<form onSubmit={onCreate} className="space-y-1 border-b p-3">
<Label htmlFor="vocab-key">{t("vocab.key")}</Label>
<div className="flex gap-2">
<Input
id="vocab-key"
value={key}
onChange={(e) => setKey(e.target.value)}
/>
<Button type="submit" size="sm" disabled={create.isPending}>
{t("vocab.create")}
</Button>
</div>
</form>
<ul className="flex-1 overflow-auto">
{isLoading && (
<li className="p-3 text-sm text-neutral-400"></li>
)}
{isError && (
<li className="p-3 text-sm text-red-600">{t("vocab.loadError")}</li>
)}
{data?.length === 0 && (
<li className="p-3 text-sm text-neutral-500">{t("vocab.empty")}</li>
)}
{data?.map((v) => (
<li key={v.id}>
<NavLink
to={`/vocabularies/${v.id}`}
className={({ isActive }) =>
`block border-b px-3 py-2 text-sm ${isActive ? "bg-indigo-50" : "hover:bg-neutral-50"}`
}
>
{v.key}
</NavLink>
</li>
))}
</ul>
</div>
);
}