feat: inline editing for activity mappings (improved UX)
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

- Edit form now appears at the position of the item being edited
- No scrolling needed - stays at same location
- Matches ActivityPage inline editing behavior
- Visual indicator: Accent border when editing
- Create form still appears at top (separate from list)

Benefits:
- Better UX - no need to scroll to top
- Easier to find edited item after saving
- Consistent with rest of app

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-21 19:46:11 +01:00
parent 829edecbdc
commit 3be82dc8c2

View File

@ -195,22 +195,18 @@ export default function AdminActivityMappingsPage() {
</div> </div>
{/* Create new button */} {/* Create new button */}
{!editingId && ( <button
<button onClick={startCreate}
onClick={startCreate} className="btn btn-primary btn-full"
className="btn btn-primary btn-full" style={{ marginBottom: 16 }}
style={{ marginBottom: 16 }} >
> <Plus size={16} /> Neues Mapping anlegen
<Plus size={16} /> Neues Mapping anlegen </button>
</button>
)}
{/* Edit form */} {/* New mapping form (only shown when creating) */}
{editingId && formData && ( {editingId === 'new' && formData && (
<div className="card" style={{ padding: 16, marginBottom: 16 }}> <div className="card" style={{ padding: 16, marginBottom: 16, border: '2px solid var(--accent)' }}>
<div style={{ fontWeight: 600, marginBottom: 12 }}> <div style={{ fontWeight: 600, marginBottom: 12 }}> Neues Mapping</div>
{editingId === 'new' ? ' Neues Mapping' : '✏️ Mapping bearbeiten'}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div> <div>
@ -220,8 +216,8 @@ export default function AdminActivityMappingsPage() {
value={formData.activity_type} value={formData.activity_type}
onChange={e => setFormData({ ...formData, activity_type: e.target.value })} onChange={e => setFormData({ ...formData, activity_type: e.target.value })}
placeholder="z.B. Traditionelles Krafttraining" placeholder="z.B. Traditionelles Krafttraining"
disabled={editingId !== 'new'}
style={{ width: '100%' }} style={{ width: '100%' }}
autoFocus
/> />
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}> <div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}>
Groß-/Kleinschreibung beachten! Muss exakt mit CSV übereinstimmen. Groß-/Kleinschreibung beachten! Muss exakt mit CSV übereinstimmen.
@ -289,65 +285,149 @@ export default function AdminActivityMappingsPage() {
</div> </div>
)} )}
{/* List */} {/* List with inline editing */}
{mappings.length === 0 ? ( {mappings.length === 0 ? (
<div className="card" style={{ padding: 40, textAlign: 'center', color: 'var(--text3)' }}> <div className="card" style={{ padding: 40, textAlign: 'center', color: 'var(--text3)' }}>
Keine Mappings gefunden Keine Mappings gefunden
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{mappings.map(mapping => ( {mappings.map(mapping => {
<div const isEditing = editingId === mapping.id
key={mapping.id}
className="card" return (
style={{ padding: 12 }} <div
> key={mapping.id}
<div style={{ className="card"
display: 'flex', style={{
alignItems: 'center', padding: 12,
gap: 8 border: isEditing ? '2px solid var(--accent)' : undefined
}}> }}
<div style={{ fontSize: 18 }}>{mapping.icon}</div> >
<div style={{ flex: 1 }}> {isEditing && formData ? (
<div style={{ fontSize: 13, fontWeight: 600 }}> /* Inline edit form */
{mapping.activity_type} <div>
<div style={{ fontWeight: 600, marginBottom: 12, color: 'var(--accent)' }}>
Mapping bearbeiten
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div>
<div className="form-label">Activity Type (nicht änderbar)</div>
<input
className="form-input"
value={formData.activity_type}
disabled
style={{ width: '100%', background: 'var(--surface2)' }}
/>
</div>
<div>
<div className="form-label">Training Type *</div>
<select
className="form-input"
value={formData.training_type_id}
onChange={e => setFormData({ ...formData, training_type_id: parseInt(e.target.value) })}
style={{ width: '100%' }}
>
{trainingTypes.map(type => (
<option key={type.id} value={type.id}>
{type.icon} {type.name_de} ({type.category})
</option>
))}
</select>
</div>
<div>
<div className="form-label">Profil-ID (leer = global)</div>
<input
className="form-input"
value={formData.profile_id}
onChange={e => setFormData({ ...formData, profile_id: e.target.value })}
placeholder="Leer lassen für globales Mapping"
style={{ width: '100%' }}
/>
</div>
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
<button
onClick={handleSave}
disabled={saving}
className="btn btn-primary"
style={{ flex: 1 }}
>
{saving ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6, justifyContent: 'center' }}>
<div className="spinner" style={{ width: 14, height: 14 }} />
Speichere...
</div>
) : (
<>
<Save size={16} /> Speichern
</>
)}
</button>
<button
onClick={cancelEdit}
disabled={saving}
className="btn btn-secondary"
style={{ flex: 1 }}
>
<X size={16} /> Abbrechen
</button>
</div>
</div>
</div> </div>
<div style={{ fontSize: 11, color: 'var(--text3)' }}> ) : (
{mapping.training_type_name_de} /* Normal view */
{mapping.profile_id && <> · User-spezifisch</>} <div style={{
{!mapping.profile_id && <> · Global</>} display: 'flex',
{mapping.source && <> · {mapping.source}</>} alignItems: 'center',
gap: 8
}}>
<div style={{ fontSize: 18 }}>{mapping.icon}</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, fontWeight: 600 }}>
{mapping.activity_type}
</div>
<div style={{ fontSize: 11, color: 'var(--text3)' }}>
{mapping.training_type_name_de}
{mapping.profile_id && <> · User-spezifisch</>}
{!mapping.profile_id && <> · Global</>}
{mapping.source && <> · {mapping.source}</>}
</div>
</div>
<button
onClick={() => startEdit(mapping)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 6,
color: 'var(--accent)'
}}
title="Bearbeiten"
>
<Pencil size={16} />
</button>
<button
onClick={() => handleDelete(mapping.id, mapping.activity_type)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 6,
color: '#D85A30'
}}
title="Löschen"
>
<Trash2 size={16} />
</button>
</div> </div>
</div> )}
<button
onClick={() => startEdit(mapping)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 6,
color: 'var(--accent)'
}}
title="Bearbeiten"
>
<Pencil size={16} />
</button>
<button
onClick={() => handleDelete(mapping.id, mapping.activity_type)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 6,
color: '#D85A30'
}}
title="Löschen"
>
<Trash2 size={16} />
</button>
</div> </div>
</div> )
))} })}
</div> </div>
)} )}