feat(web): rename vocabularies + edit/delete terms in place (#30)

This commit is contained in:
2026-06-05 20:27:50 +02:00
parent 65ca79f2bd
commit 83ca506702
4 changed files with 176 additions and 14 deletions
+59 -10
View File
@@ -2,7 +2,8 @@ import { useState, type FormEvent } from "react";
import { NavLink } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useVocabularies, useCreateVocabulary } from "../api/queries";
import { useVocabularies, useCreateVocabulary, useRenameVocabulary, useDeleteVocabulary } from "../api/queries";
import { DeleteConfirmDialog } from "../components/delete-confirm-dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -13,8 +14,12 @@ export function VocabularyList() {
const { data, isLoading, isError } = useVocabularies();
const create = useCreateVocabulary();
const renameVocabulary = useRenameVocabulary();
const deleteVocabulary = useDeleteVocabulary();
const [key, setKey] = useState("");
const [editingId, setEditingId] = useState<string | null>(null);
const [draftKey, setDraftKey] = useState("");
const onCreate = (event: FormEvent) => {
event.preventDefault();
@@ -56,15 +61,59 @@ export function VocabularyList() {
<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 key={v.id} className="flex items-center gap-1 border-b pr-2">
{editingId === v.id ? (
<form
className="flex flex-1 flex-wrap gap-1 p-1"
onSubmit={(e) => {
e.preventDefault();
renameVocabulary.mutate(
{ id: v.id, key: draftKey.trim() },
{ onSuccess: () => setEditingId(null) },
);
}}
>
<Input
aria-label={t("vocab.key")}
value={draftKey}
onChange={(e) => setDraftKey(e.target.value)}
/>
<Button type="submit" size="sm" disabled={renameVocabulary.isPending}>
{t("actions.save")}
</Button>
<Button type="button" variant="ghost" size="sm" onClick={() => setEditingId(null)}>
{t("form.cancel")}
</Button>
{renameVocabulary.isError && (
<p role="alert" className="text-xs text-red-600">
{t("form.rejected")}
</p>
)}
</form>
) : (
<>
<NavLink
to={`/vocabularies/${v.id}`}
className={({ isActive }) =>
`block flex-1 px-3 py-2 text-sm ${isActive ? "bg-indigo-50" : "hover:bg-neutral-50"}`
}
>
{v.key}
</NavLink>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => { setEditingId(v.id); setDraftKey(v.key); }}
>
{t("actions.rename")}
</Button>
<DeleteConfirmDialog
description={t("actions.confirmDeleteVocabulary")}
onConfirm={() => deleteVocabulary.mutateAsync(v.id)}
/>
</>
)}
</li>
))}
</ul>