Compare commits
2 Commits
840996fce9
...
11e3ce8770
| Author | SHA1 | Date | |
|---|---|---|---|
| 11e3ce8770 | |||
| 2a6d203d1c |
@@ -42,7 +42,7 @@ function buildRows(content) {
|
|||||||
} else if (type === 'word') {
|
} else if (type === 'word') {
|
||||||
rows.push({ kind: 'lang', label: 'Positiv-Wörter', color: 'text-green-700',
|
rows.push({ kind: 'lang', label: 'Positiv-Wörter', color: 'text-green-700',
|
||||||
cell: wordsCell(content.positive) });
|
cell: wordsCell(content.positive) });
|
||||||
if (content.negative?.words?.length)
|
// 'word' braucht laut Datenmodell Negativ-Wörter → Zeile immer zeigen, fehlende sichtbar machen
|
||||||
rows.push({ kind: 'lang', label: 'Negativ-Wörter', color: 'text-red-600',
|
rows.push({ kind: 'lang', label: 'Negativ-Wörter', color: 'text-red-600',
|
||||||
cell: wordsCell(content.negative) });
|
cell: wordsCell(content.negative) });
|
||||||
} else {
|
} else {
|
||||||
@@ -50,9 +50,10 @@ function buildRows(content) {
|
|||||||
if (content.positive)
|
if (content.positive)
|
||||||
rows.push({ kind: 'lang', label: 'Positiv', color: 'text-green-700',
|
rows.push({ kind: 'lang', label: 'Positiv', color: 'text-green-700',
|
||||||
cell: sentenceCell(content.positive, 'positive_sentence') });
|
cell: sentenceCell(content.positive, 'positive_sentence') });
|
||||||
const negHasContent = content.negative &&
|
// 'question' = Frage + Positiv + Negativ → Negativ-Zeile immer zeigen, auch wenn leer
|
||||||
LANGS.some(l => (content.negative.sentence?.[`negative_sentence_${l}`] || '').trim());
|
// ('fehlt' statt stilles Ausblenden, damit eine fehlende Negativ-Antwort auffällt).
|
||||||
if (negHasContent)
|
// 'text' hat per Definition kein Negativ.
|
||||||
|
if (type === 'question')
|
||||||
rows.push({ kind: 'lang', label: 'Negativ', color: 'text-red-600',
|
rows.push({ kind: 'lang', label: 'Negativ', color: 'text-red-600',
|
||||||
cell: sentenceCell(content.negative, 'negative_sentence') });
|
cell: sentenceCell(content.negative, 'negative_sentence') });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1180,12 +1180,90 @@ function AutoCreateAllButton({ currentPicture, objects }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Geführter Review-Flow (Wizard) ──────────────────────────────────────────
|
||||||
|
// Läuft die Pairs des ausgewählten Objekts der Reihe nach durch. Wiederverwendung:
|
||||||
|
// EditPairForm (Editor + alle Aktionen) und PairReviewModal (Übersetzungs-Prüf-Grid).
|
||||||
|
|
||||||
|
function PairReviewWizard({ pairs, allObjects, onClose, onPairsReload }) {
|
||||||
|
const [queue] = useState(() => pairs); // Snapshot — stabile Navigation
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
const [reviewData, setReviewData] = useState(null); // { pair, content } | null
|
||||||
|
const pair = queue[index];
|
||||||
|
|
||||||
|
function advance() {
|
||||||
|
if (index + 1 < queue.length) { setIndex(index + 1); setReviewData(null); }
|
||||||
|
else onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTranslate(p) {
|
||||||
|
const res = await apiPost(`/pairs/${p.id}/translate`, {});
|
||||||
|
setReviewData({ pair: p, content: res.content });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRetranslate(p) {
|
||||||
|
const res = await apiPost(`/pairs/${p.id}/translate`, { overwrite: true });
|
||||||
|
setReviewData({ pair: p, content: res.content });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pair) { onClose(); return null; }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-40 flex items-start justify-center bg-black/40 backdrop-blur-sm p-4 overflow-y-auto">
|
||||||
|
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg my-8 flex flex-col">
|
||||||
|
{/* Header mit Fortschritt + Navigation */}
|
||||||
|
<div className="flex items-center gap-2 px-5 py-3 border-b border-slate-200">
|
||||||
|
<span className="text-lg">🚀</span>
|
||||||
|
<span className="font-semibold text-slate-800 text-sm">
|
||||||
|
Pair {index + 1}/{queue.length}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs bg-slate-200 text-slate-600 rounded px-1.5 py-0.5">{pair.answer_type}</span>
|
||||||
|
<span className="font-mono text-xs text-slate-400">{pair.id?.slice(0, 8)}…</span>
|
||||||
|
<div className="ml-auto flex items-center gap-1.5">
|
||||||
|
<button onClick={() => setIndex(i => Math.max(0, i - 1))} disabled={index === 0}
|
||||||
|
className="text-xs px-2 py-1 rounded-lg text-slate-500 hover:bg-slate-100 disabled:opacity-30"
|
||||||
|
title="Vorheriges Pair">‹ Zurück</button>
|
||||||
|
<button onClick={advance}
|
||||||
|
className="text-xs px-2 py-1 rounded-lg text-slate-500 hover:bg-slate-100"
|
||||||
|
title="Ohne Änderung zum nächsten Pair">Überspringen →</button>
|
||||||
|
<button onClick={onClose}
|
||||||
|
className="text-slate-400 hover:text-slate-600 text-xl leading-none px-1" aria-label="Flow beenden">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body — vollständiger Editor pro Pair (remountet via key) */}
|
||||||
|
<div className="px-5 py-4 overflow-y-auto">
|
||||||
|
<EditPairForm
|
||||||
|
key={pair.id}
|
||||||
|
pair={pair}
|
||||||
|
allObjects={allObjects}
|
||||||
|
onSaved={() => { onPairsReload(); advance(); }}
|
||||||
|
onCancel={onClose}
|
||||||
|
onDeleted={() => { onPairsReload(); advance(); }}
|
||||||
|
onSavedAndTranslate={(savedPair) => handleTranslate(savedPair)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{reviewData && (
|
||||||
|
<PairReviewModal
|
||||||
|
pair={reviewData.pair}
|
||||||
|
content={reviewData.content}
|
||||||
|
onClose={() => setReviewData(null)}
|
||||||
|
onDone={() => { onPairsReload(); advance(); }}
|
||||||
|
onRetranslate={() => handleRetranslate(reviewData.pair)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Right panel: Pairs ───────────────────────────────────────────────────────
|
// ─── Right panel: Pairs ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
function PairsPanel({ selectedObject, allObjects, objectPairs, loadingPairs, onPairSaved, onPairsReload, onReloadAll }) {
|
function PairsPanel({ selectedObject, allObjects, objectPairs, loadingPairs, onPairSaved, onPairsReload, onReloadAll }) {
|
||||||
const [editingId, setEditingId] = useState(null);
|
const [editingId, setEditingId] = useState(null);
|
||||||
const [translatingId, setTranslatingId] = useState(null);
|
const [translatingId, setTranslatingId] = useState(null);
|
||||||
const [reviewData, setReviewData] = useState(null); // { pair, content }
|
const [reviewData, setReviewData] = useState(null); // { pair, content }
|
||||||
|
const [wizardOpen, setWizardOpen] = useState(false);
|
||||||
|
|
||||||
async function handleTranslate(pair) {
|
async function handleTranslate(pair) {
|
||||||
setTranslatingId(pair.id);
|
setTranslatingId(pair.id);
|
||||||
@@ -1214,10 +1292,17 @@ function PairsPanel({ selectedObject, allObjects, objectPairs, loadingPairs, onP
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="w-2/5 border-l border-slate-200 bg-white flex flex-col overflow-hidden">
|
<aside className="w-2/5 border-l border-slate-200 bg-white flex flex-col overflow-hidden">
|
||||||
<div className="px-3 py-2.5 border-b border-slate-100 bg-slate-50 flex-shrink-0">
|
<div className="px-3 py-2.5 border-b border-slate-100 bg-slate-50 flex-shrink-0 flex items-center gap-2">
|
||||||
<h2 className="text-xs font-bold text-slate-500 uppercase tracking-wider">
|
<h2 className="text-xs font-bold text-slate-500 uppercase tracking-wider">
|
||||||
Pairs — Objekt #{selectedObject._index + 1}
|
Pairs — Objekt #{selectedObject._index + 1}
|
||||||
</h2>
|
</h2>
|
||||||
|
{objectPairs.length > 0 && (
|
||||||
|
<button onClick={() => setWizardOpen(true)}
|
||||||
|
className="ml-auto text-xs font-medium text-violet-700 bg-violet-50 hover:bg-violet-100 px-2 py-0.5 rounded transition-colors"
|
||||||
|
title="Alle Pairs dieses Objekts nacheinander prüfen">
|
||||||
|
🚀 Review-Flow starten
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<div className="p-3 border-b border-slate-100">
|
<div className="p-3 border-b border-slate-100">
|
||||||
@@ -1291,6 +1376,15 @@ function PairsPanel({ selectedObject, allObjects, objectPairs, loadingPairs, onP
|
|||||||
onRetranslate={() => handleRetranslate(reviewData.pair)}
|
onRetranslate={() => handleRetranslate(reviewData.pair)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{wizardOpen && (
|
||||||
|
<PairReviewWizard
|
||||||
|
pairs={objectPairs}
|
||||||
|
allObjects={allObjects}
|
||||||
|
onClose={() => { setWizardOpen(false); (onReloadAll || onPairsReload)(); }}
|
||||||
|
onPairsReload={onPairsReload}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user