feat: CRM-Dashboard, Content-Verwaltung und Wort-Autocomplete

- Home-Seite nach Login mit Begrüßung und 3 Kacheln (Content erstellen, Content verwalten, User verwalten)
- AuthContext speichert User-Profil + Rolle; AdminRoute blockt Nicht-Admins
- Content verwalten (admin-only): Status-Dashboard pro Collection, Liste/Kachel-View, generisches Edit-Formular
- Nur aktive db_-Collections im Dashboard (alte pictures/objects/words/questions entfernt)
- Wort-Autocomplete in DrawIt: ab dem ersten Buchstaben Vorschläge aus db_words, Tastatur-Navigation, Duplikat-Filter
- Backend: /users/me Proxy, db-words/search Endpoint, generische Collection-Endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 23:37:48 +02:00
parent 05c62ac414
commit d02788bd0e
13 changed files with 1962 additions and 23 deletions

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'
import DrawCanvas, { type DrawCanvasHandle } from '../components/DrawCanvas'
import BlurhashCanvas from '../components/BlurhashCanvas'
import Topbar from '../components/Topbar'
import WordAutocomplete from '../components/WordAutocomplete'
import {
getDbPictures,
updateDbPicture,
@@ -362,13 +363,14 @@ export default function DrawIt() {
))}
</div>
<div style={{ display: 'flex', gap: 4 }}>
<input
type="text"
<WordAutocomplete
value={pictureWordInput}
onChange={e => setPictureWordInput(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleAddPictureWord() }}
onChange={setPictureWordInput}
onSubmit={handleAddPictureWord}
token={token}
placeholder="Hauptwort hinzufügen…"
style={{ flex: 1, padding: '4px 8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text-1)', fontFamily: 'var(--font)', fontSize: 12 }}
excludeTitles={pictureWords.map(w => w.titel_de)}
inputStyle={{ flex: 1, padding: '4px 8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text-1)', fontFamily: 'var(--font)', fontSize: 12, width: '100%' }}
/>
<button
onClick={handleAddPictureWord}
@@ -654,17 +656,18 @@ export default function DrawIt() {
{/* Add word input */}
<div style={{ display:'flex', gap:4 }}>
<input
type="text"
<WordAutocomplete
value={selectedObjectId ? (wordInputs[selectedObjectId] || '') : newWordInput}
onChange={e => selectedObjectId
? setWordInputs(prev => ({ ...prev, [selectedObjectId]: e.target.value }))
: setNewWordInput(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter') selectedObjectId ? handleAddObjectWord(selectedObjectId) : handleAddPendingWord()
}}
onChange={v => selectedObjectId
? setWordInputs(prev => ({ ...prev, [selectedObjectId]: v }))
: setNewWordInput(v)}
onSubmit={() => selectedObjectId ? handleAddObjectWord(selectedObjectId) : handleAddPendingWord()}
token={token}
placeholder="Wort hinzufügen…"
style={{ flex:1, padding:'4px 8px', borderRadius:'var(--r-sm)', border:'1px solid var(--border)', background:'var(--surface-2)', color:'var(--text-1)', fontFamily:'var(--font)', fontSize:12 }}
excludeTitles={selectedObjectId
? (objectWords[selectedObjectId] || []).map(w => w.titel_de)
: pendingWords}
inputStyle={{ flex:1, padding:'4px 8px', borderRadius:'var(--r-sm)', border:'1px solid var(--border)', background:'var(--surface-2)', color:'var(--text-1)', fontFamily:'var(--font)', fontSize:12, width: '100%' }}
/>
<button
onClick={() => selectedObjectId ? handleAddObjectWord(selectedObjectId) : handleAddPendingWord()}