feat: inline editing for activity mappings (improved UX)
- 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:
parent
829edecbdc
commit
3be82dc8c2
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user