import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { Pencil, Trash2, Plus, Save, X, ArrowLeft, TrendingUp } from 'lucide-react' import { api } from '../utils/api' /** * AdminActivityMappingsPage - Manage activity_type → training_type mappings * v9d Phase 1b - Learnable system (replaces hardcoded mappings) */ export default function AdminActivityMappingsPage() { const nav = useNavigate() const [mappings, setMappings] = useState([]) const [trainingTypes, setTrainingTypes] = useState([]) const [coverage, setCoverage] = useState(null) const [loading, setLoading] = useState(true) const [editingId, setEditingId] = useState(null) const [formData, setFormData] = useState(null) const [error, setError] = useState(null) const [saving, setSaving] = useState(false) const [filter, setFilter] = useState('all') // 'all', 'global', 'user' useEffect(() => { load() }, [filter]) const load = () => { setLoading(true) Promise.all([ api.adminListActivityMappings(null, filter === 'global'), api.listTrainingTypesFlat(), api.adminGetMappingCoverage() ]).then(([mappingsData, typesData, coverageData]) => { setMappings(mappingsData) setTrainingTypes(typesData) setCoverage(coverageData) setLoading(false) }).catch(err => { console.error('Failed to load mappings:', err) setError(err.message) setLoading(false) }) } const startCreate = () => { setFormData({ activity_type: '', training_type_id: trainingTypes[0]?.id || null, profile_id: '', source: 'admin' }) setEditingId('new') } const startEdit = (mapping) => { setFormData({ activity_type: mapping.activity_type, training_type_id: mapping.training_type_id, profile_id: mapping.profile_id || '', source: mapping.source }) setEditingId(mapping.id) } const cancelEdit = () => { setEditingId(null) setFormData(null) setError(null) } const handleSave = async () => { if (!formData.activity_type || !formData.training_type_id) { setError('Activity Type und Training Type sind Pflichtfelder') return } setSaving(true) setError(null) try { const payload = { ...formData, profile_id: formData.profile_id || null } if (editingId === 'new') { await api.adminCreateActivityMapping(payload) } else { await api.adminUpdateActivityMapping(editingId, { training_type_id: payload.training_type_id, profile_id: payload.profile_id, source: payload.source }) } await load() cancelEdit() } catch (err) { console.error('Save failed:', err) setError(err.message) } finally { setSaving(false) } } const handleDelete = async (id, activityType) => { if (!confirm(`Mapping für "${activityType}" wirklich löschen?\n\nZukünftige Imports werden diesen Typ nicht mehr automatisch zuordnen.`)) { return } try { await api.adminDeleteActivityMapping(id) await load() } catch (err) { alert('Löschen fehlgeschlagen: ' + err.message) } } if (loading) { return (
) } const coveragePercent = coverage ? Math.round((coverage.mapped_activities / coverage.total_activities) * 100) : 0 return (

Activity-Mappings

{error && (
{error}
)} {/* Coverage Stats */} {coverage && (
Mapping-Abdeckung
{coveragePercent}%
Zugeordnet
{coverage.mapped_activities}
Mit Typ
{coverage.unmapped_activities}
Ohne Typ
{coverage.unmapped_types} verschiedene Activity-Types noch nicht gemappt
)} {/* Filter */}
{/* Create new button */} {/* New mapping form (only shown when creating) */} {editingId === 'new' && formData && (
➕ Neues Mapping
Activity Type * (exakt wie in CSV)
setFormData({ ...formData, activity_type: e.target.value })} placeholder="z.B. Traditionelles Krafttraining" style={{ width: '100%' }} autoFocus />
Groß-/Kleinschreibung beachten! Muss exakt mit CSV übereinstimmen.
Training Type *
Profil-ID (leer = global)
setFormData({ ...formData, profile_id: e.target.value })} placeholder="Leer lassen für globales Mapping" style={{ width: '100%' }} />
Global = für alle User, sonst user-spezifisch
)} {/* List with inline editing */} {mappings.length === 0 ? (
Keine Mappings gefunden
) : (
{mappings.map(mapping => { const isEditing = editingId === mapping.id return (
{isEditing && formData ? ( /* Inline edit form */
✏️ Mapping bearbeiten
Activity Type (nicht änderbar)
Training Type *
Profil-ID (leer = global)
setFormData({ ...formData, profile_id: e.target.value })} placeholder="Leer lassen für globales Mapping" style={{ width: '100%' }} />
) : ( /* Normal view */
{mapping.icon}
{mapping.activity_type}
→ {mapping.training_type_name_de} {mapping.profile_id && <> · User-spezifisch} {!mapping.profile_id && <> · Global} {mapping.source && <> · {mapping.source}}
)}
) })}
)}
💡 Tipp: Das System lernt automatisch! Wenn du im Tab "Kategorisieren" Aktivitäten zuordnest, wird das Mapping gespeichert und beim nächsten Import automatisch angewendet.
) }