feat: Objekt inline bearbeiten in GenerateIt (Notizen + Wörter)
✏️ Button pro Objekt in der linken Sidebar öffnet Edit-Panel: - Notizen-Textarea bearbeitbar - Wörter: Chips mit × + neues Wort hinzufügen (Enter oder +) - Speichern / Abbrechen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@ import {
|
||||
getDbObjects,
|
||||
getDbObjectPairs,
|
||||
getDbObjectWords,
|
||||
updateDbObject,
|
||||
addDbObjectWord,
|
||||
deleteDbObjectWord,
|
||||
createDbPair,
|
||||
updateDbPair,
|
||||
deleteDbPair,
|
||||
@@ -417,6 +420,11 @@ export default function GenerateIt() {
|
||||
const [pairsLoading, setPairsLoading] = useState(false)
|
||||
// per-object words: objectId → DbWord[]
|
||||
const [objectWords, setObjectWords] = useState<Record<string, DbWord[]>>({})
|
||||
// inline object edit
|
||||
const [editingObjId, setEditingObjId] = useState<string | null>(null)
|
||||
const [editNotes, setEditNotes] = useState('')
|
||||
const [editWordInput, setEditWordInput] = useState('')
|
||||
const [savingObj, setSavingObj] = useState(false)
|
||||
|
||||
const currentPicture: DbPicture | null =
|
||||
currentIndex >= 0 && currentIndex < pictureList.length ? pictureList[currentIndex] : null
|
||||
@@ -491,6 +499,44 @@ export default function GenerateIt() {
|
||||
.finally(() => setPairsLoading(false))
|
||||
}
|
||||
|
||||
const startEditObj = (obj: DbObject) => {
|
||||
setEditingObjId(obj.id)
|
||||
setEditNotes(obj.user_notes ?? '')
|
||||
setEditWordInput('')
|
||||
}
|
||||
|
||||
const cancelEditObj = () => { setEditingObjId(null); setEditWordInput('') }
|
||||
|
||||
const saveEditObj = async (objId: string) => {
|
||||
if (!token) return
|
||||
setSavingObj(true)
|
||||
try {
|
||||
await updateDbObject(objId, { user_notes: editNotes.trim() || null }, token)
|
||||
setDbObjects(prev => prev.map(o => o.id === objId ? { ...o, user_notes: editNotes.trim() || null } : o))
|
||||
setEditingObjId(null)
|
||||
} finally {
|
||||
setSavingObj(false)
|
||||
}
|
||||
}
|
||||
|
||||
const addWordToObj = async (objId: string) => {
|
||||
if (!token) return
|
||||
const titel_de = editWordInput.trim()
|
||||
if (!titel_de) return
|
||||
const result = await addDbObjectWord(objId, { titel_de, level: 50 }, token)
|
||||
if (result.ok) {
|
||||
const words = await getDbObjectWords(objId, token)
|
||||
setObjectWords(prev => ({ ...prev, [objId]: words }))
|
||||
setEditWordInput('')
|
||||
}
|
||||
}
|
||||
|
||||
const removeWordFromObj = async (objId: string, junctionId: string | number) => {
|
||||
if (!token) return
|
||||
await deleteDbObjectWord(objId, junctionId, token)
|
||||
setObjectWords(prev => ({ ...prev, [objId]: (prev[objId] || []).filter(w => w.junction_id !== junctionId) }))
|
||||
}
|
||||
|
||||
const imageNav = (
|
||||
<div className="image-nav">
|
||||
<button className="btn-icon" onClick={() => setCurrentIndex(i => i - 1)} disabled={currentIndex <= 0}>
|
||||
@@ -546,12 +592,62 @@ export default function GenerateIt() {
|
||||
<span>{obj.selections?.length ? `${obj.selections.length} Auswahl(en)` : '–'}</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={e => { e.stopPropagation(); editingObjId === obj.id ? cancelEditObj() : startEditObj(obj) }}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text-2)', padding: '0 3px', flexShrink: 0 }}
|
||||
title="Bearbeiten"
|
||||
>✏️</button>
|
||||
</div>
|
||||
{obj.user_notes && (
|
||||
|
||||
{obj.user_notes && editingObjId !== obj.id && (
|
||||
<div style={{ padding: '4px 8px 6px 12px', fontSize: 11.5, color: 'var(--text-2)', lineHeight: 1.4, borderTop: '1px solid var(--border)' }}>
|
||||
{obj.user_notes}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Inline edit panel */}
|
||||
{editingObjId === obj.id && (
|
||||
<div style={{ padding: '8px', borderTop: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 6 }} onClick={e => e.stopPropagation()}>
|
||||
{/* Notizen */}
|
||||
<textarea
|
||||
value={editNotes}
|
||||
onChange={e => setEditNotes(e.target.value)}
|
||||
rows={3}
|
||||
placeholder="Notizen…"
|
||||
style={{ width: '100%', resize: 'vertical', padding: '5px 7px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text-1)', fontFamily: 'var(--font)', fontSize: 12, boxSizing: 'border-box' }}
|
||||
/>
|
||||
{/* Wörter */}
|
||||
<div>
|
||||
<div style={{ fontSize: 10, color: 'var(--text-2)', marginBottom: 3 }}>Wörter</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 3, marginBottom: 4 }}>
|
||||
{(objectWords[obj.id] || []).map(w => (
|
||||
<span key={w.junction_id} style={{ display: 'flex', alignItems: 'center', gap: 2, padding: '2px 7px', background: '#e0e7ff', color: '#3730a3', borderRadius: 9999, fontSize: 11 }}>
|
||||
{w.titel_de}
|
||||
<button onClick={() => removeWordFromObj(obj.id, w.junction_id!)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#818cf8', padding: 0, fontSize: 13, lineHeight: 1 }}>×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<input
|
||||
type="text"
|
||||
value={editWordInput}
|
||||
onChange={e => setEditWordInput(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') addWordToObj(obj.id) }}
|
||||
placeholder="Wort hinzufügen…"
|
||||
style={{ flex: 1, padding: '3px 7px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text-1)', fontFamily: 'var(--font)', fontSize: 12 }}
|
||||
/>
|
||||
<button onClick={() => addWordToObj(obj.id)} style={{ padding: '3px 8px', borderRadius: 'var(--r-sm)', background: '#6366f1', color: '#fff', border: 'none', cursor: 'pointer', fontSize: 12 }}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Aktionen */}
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<button className="btn-primary btn-sm" style={{ flex: 1 }} onClick={() => saveEditObj(obj.id)} disabled={savingObj}>
|
||||
{savingObj ? 'Speichere…' : 'Speichern'}
|
||||
</button>
|
||||
<button className="btn-ghost btn-sm" onClick={cancelEditObj}>Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user