feat: Frontend Phase 3.1 - Focus Areas Admin UI
- AdminFocusAreasPage: Full CRUD for focus area definitions - Route: /admin/focus-areas - AdminPanel: Link zu Focus Areas (neben Goal Types) - api.js: 7 neue Focus Area Endpoints Features: - Category-grouped display (7 categories) - Inline editing - Active/Inactive toggle - Create form with validation - Show/Hide inactive areas Next: Goal Form Multi-Select
This commit is contained in:
parent
f312dd0dbb
commit
d14157f7ad
|
|
@ -32,6 +32,7 @@ import AdminActivityMappingsPage from './pages/AdminActivityMappingsPage'
|
||||||
import AdminTrainingProfiles from './pages/AdminTrainingProfiles'
|
import AdminTrainingProfiles from './pages/AdminTrainingProfiles'
|
||||||
import AdminPromptsPage from './pages/AdminPromptsPage'
|
import AdminPromptsPage from './pages/AdminPromptsPage'
|
||||||
import AdminGoalTypesPage from './pages/AdminGoalTypesPage'
|
import AdminGoalTypesPage from './pages/AdminGoalTypesPage'
|
||||||
|
import AdminFocusAreasPage from './pages/AdminFocusAreasPage'
|
||||||
import SubscriptionPage from './pages/SubscriptionPage'
|
import SubscriptionPage from './pages/SubscriptionPage'
|
||||||
import SleepPage from './pages/SleepPage'
|
import SleepPage from './pages/SleepPage'
|
||||||
import RestDaysPage from './pages/RestDaysPage'
|
import RestDaysPage from './pages/RestDaysPage'
|
||||||
|
|
@ -192,6 +193,7 @@ function AppShell() {
|
||||||
<Route path="/admin/training-profiles" element={<AdminTrainingProfiles/>}/>
|
<Route path="/admin/training-profiles" element={<AdminTrainingProfiles/>}/>
|
||||||
<Route path="/admin/prompts" element={<AdminPromptsPage/>}/>
|
<Route path="/admin/prompts" element={<AdminPromptsPage/>}/>
|
||||||
<Route path="/admin/goal-types" element={<AdminGoalTypesPage/>}/>
|
<Route path="/admin/goal-types" element={<AdminGoalTypesPage/>}/>
|
||||||
|
<Route path="/admin/focus-areas" element={<AdminFocusAreasPage/>}/>
|
||||||
<Route path="/subscription" element={<SubscriptionPage/>}/>
|
<Route path="/subscription" element={<SubscriptionPage/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
475
frontend/src/pages/AdminFocusAreasPage.jsx
Normal file
475
frontend/src/pages/AdminFocusAreasPage.jsx
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { Plus, Pencil, Trash2, Save, X, Eye, EyeOff } from 'lucide-react'
|
||||||
|
import { api } from '../utils/api'
|
||||||
|
|
||||||
|
const CATEGORIES = [
|
||||||
|
{ value: 'body_composition', label: 'Körperzusammensetzung' },
|
||||||
|
{ value: 'training', label: 'Training' },
|
||||||
|
{ value: 'endurance', label: 'Ausdauer' },
|
||||||
|
{ value: 'coordination', label: 'Koordination' },
|
||||||
|
{ value: 'mental', label: 'Mental' },
|
||||||
|
{ value: 'recovery', label: 'Erholung' },
|
||||||
|
{ value: 'health', label: 'Gesundheit' },
|
||||||
|
{ value: 'custom', label: 'Eigene' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function AdminFocusAreasPage() {
|
||||||
|
const [data, setData] = useState({ areas: [], grouped: {}, total: 0 })
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
const [showInactive, setShowInactive] = useState(false)
|
||||||
|
const [editingId, setEditingId] = useState(null)
|
||||||
|
const [creating, setCreating] = useState(false)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
key: '',
|
||||||
|
name_de: '',
|
||||||
|
name_en: '',
|
||||||
|
icon: '',
|
||||||
|
description: '',
|
||||||
|
category: 'custom'
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData()
|
||||||
|
}, [showInactive])
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const result = await api.listFocusAreaDefinitions(showInactive)
|
||||||
|
setData(result)
|
||||||
|
setError(null)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load focus areas:', err)
|
||||||
|
setError(err.message)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = async () => {
|
||||||
|
if (!formData.key || !formData.name_de) {
|
||||||
|
setError('Key und Name (DE) sind erforderlich')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.createFocusAreaDefinition(formData)
|
||||||
|
setCreating(false)
|
||||||
|
setFormData({
|
||||||
|
key: '',
|
||||||
|
name_de: '',
|
||||||
|
name_en: '',
|
||||||
|
icon: '',
|
||||||
|
description: '',
|
||||||
|
category: 'custom'
|
||||||
|
})
|
||||||
|
await loadData()
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdate = async (id) => {
|
||||||
|
try {
|
||||||
|
const area = data.areas.find(a => a.id === id)
|
||||||
|
await api.updateFocusAreaDefinition(id, {
|
||||||
|
name_de: area.name_de,
|
||||||
|
name_en: area.name_en,
|
||||||
|
icon: area.icon,
|
||||||
|
description: area.description,
|
||||||
|
category: area.category,
|
||||||
|
is_active: area.is_active
|
||||||
|
})
|
||||||
|
setEditingId(null)
|
||||||
|
await loadData()
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
if (!confirm('Focus Area wirklich löschen?')) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.deleteFocusAreaDefinition(id)
|
||||||
|
await loadData()
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToggleActive = async (id) => {
|
||||||
|
const area = data.areas.find(a => a.id === id)
|
||||||
|
try {
|
||||||
|
await api.updateFocusAreaDefinition(id, {
|
||||||
|
is_active: !area.is_active
|
||||||
|
})
|
||||||
|
await loadData()
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateField = (id, field, value) => {
|
||||||
|
setData(prev => ({
|
||||||
|
...prev,
|
||||||
|
areas: prev.areas.map(a =>
|
||||||
|
a.id === id ? { ...a, [field]: value } : a
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 20, textAlign: 'center' }}>
|
||||||
|
<div className="spinner" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 16, paddingBottom: 80 }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 16
|
||||||
|
}}>
|
||||||
|
<h1 style={{ fontSize: 24, fontWeight: 700, margin: 0 }}>
|
||||||
|
🎯 Focus Areas ({data.total})
|
||||||
|
</h1>
|
||||||
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
|
<button
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => setShowInactive(!showInactive)}
|
||||||
|
style={{ padding: '8px 12px', fontSize: 13 }}
|
||||||
|
>
|
||||||
|
{showInactive ? <Eye size={14} /> : <EyeOff size={14} />}
|
||||||
|
{showInactive ? 'Inaktive ausblenden' : 'Inaktive anzeigen'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-primary"
|
||||||
|
onClick={() => setCreating(true)}
|
||||||
|
style={{ padding: '8px 16px' }}
|
||||||
|
>
|
||||||
|
<Plus size={16} /> Neue Focus Area
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div style={{
|
||||||
|
padding: 12,
|
||||||
|
background: '#FEE2E2',
|
||||||
|
color: '#991B1B',
|
||||||
|
borderRadius: 8,
|
||||||
|
marginBottom: 16,
|
||||||
|
fontSize: 14
|
||||||
|
}}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Create Form */}
|
||||||
|
{creating && (
|
||||||
|
<div className="card" style={{ marginBottom: 16, background: 'var(--accent-light)' }}>
|
||||||
|
<h3 style={{ fontSize: 16, marginBottom: 12, color: 'var(--accent)' }}>
|
||||||
|
Neue Focus Area
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gap: 12 }}>
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 13, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Key (Eindeutig, z.B. "explosive_power")
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="form-input"
|
||||||
|
value={formData.key}
|
||||||
|
onChange={(e) => setFormData({ ...formData, key: e.target.value })}
|
||||||
|
placeholder="explosive_power"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 13, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Name (Deutsch) *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="form-input"
|
||||||
|
value={formData.name_de}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name_de: e.target.value })}
|
||||||
|
placeholder="Explosivkraft"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 13, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Name (English)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="form-input"
|
||||||
|
value={formData.name_en}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name_en: e.target.value })}
|
||||||
|
placeholder="Explosive Power"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 13, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Icon (Emoji)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="form-input"
|
||||||
|
value={formData.icon}
|
||||||
|
onChange={(e) => setFormData({ ...formData, icon: e.target.value })}
|
||||||
|
placeholder="💥"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 13, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Kategorie
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className="form-input"
|
||||||
|
value={formData.category}
|
||||||
|
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{CATEGORIES.map(cat => (
|
||||||
|
<option key={cat.value} value={cat.value}>{cat.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 13, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Beschreibung
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
placeholder="Kraft in kürzester Zeit explosiv entfalten"
|
||||||
|
rows={2}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
|
<button className="btn-primary" onClick={handleCreate} style={{ flex: 1 }}>
|
||||||
|
<Save size={14} /> Erstellen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setCreating(false)
|
||||||
|
setFormData({
|
||||||
|
key: '',
|
||||||
|
name_de: '',
|
||||||
|
name_en: '',
|
||||||
|
icon: '',
|
||||||
|
description: '',
|
||||||
|
category: 'custom'
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<X size={14} /> Abbrechen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Grouped Areas */}
|
||||||
|
{Object.entries(data.grouped).map(([category, areas]) => (
|
||||||
|
<div key={category} style={{ marginBottom: 24 }}>
|
||||||
|
<h2 style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--text2)',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.05em',
|
||||||
|
marginBottom: 12
|
||||||
|
}}>
|
||||||
|
{CATEGORIES.find(c => c.value === category)?.label || category} ({areas.length})
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||||
|
{areas.map(area => {
|
||||||
|
const isEditing = editingId === area.id
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={area.id}
|
||||||
|
className="card"
|
||||||
|
style={{
|
||||||
|
opacity: area.is_active ? 1 : 0.5,
|
||||||
|
borderLeft: area.is_active
|
||||||
|
? '4px solid var(--accent)'
|
||||||
|
: '4px solid var(--border)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEditing ? (
|
||||||
|
<div style={{ display: 'grid', gap: 12 }}>
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Name (DE)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="form-input"
|
||||||
|
value={area.name_de}
|
||||||
|
onChange={(e) => updateField(area.id, 'name_de', e.target.value)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Icon
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="form-input"
|
||||||
|
value={area.icon || ''}
|
||||||
|
onChange={(e) => updateField(area.id, 'icon', e.target.value)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 }}>
|
||||||
|
Beschreibung
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
value={area.description || ''}
|
||||||
|
onChange={(e) => updateField(area.id, 'description', e.target.value)}
|
||||||
|
rows={2}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
|
<button
|
||||||
|
className="btn-primary"
|
||||||
|
onClick={() => handleUpdate(area.id)}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<Save size={14} /> Speichern
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingId(null)
|
||||||
|
loadData()
|
||||||
|
}}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<X size={14} /> Abbrechen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: 12
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: 4,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8
|
||||||
|
}}>
|
||||||
|
{area.icon && <span>{area.icon}</span>}
|
||||||
|
<span>{area.name_de}</span>
|
||||||
|
{!area.is_active && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: 11,
|
||||||
|
padding: '2px 6px',
|
||||||
|
background: 'var(--border)',
|
||||||
|
borderRadius: 4,
|
||||||
|
color: 'var(--text3)'
|
||||||
|
}}>
|
||||||
|
Inaktiv
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 4 }}>
|
||||||
|
Key: <code style={{
|
||||||
|
background: 'var(--surface2)',
|
||||||
|
padding: '2px 4px',
|
||||||
|
borderRadius: 4
|
||||||
|
}}>
|
||||||
|
{area.key}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{area.description && (
|
||||||
|
<div style={{ fontSize: 13, color: 'var(--text2)', marginTop: 4 }}>
|
||||||
|
{area.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
|
||||||
|
<button
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => handleToggleActive(area.id)}
|
||||||
|
style={{ padding: '6px 12px' }}
|
||||||
|
title={area.is_active ? 'Deaktivieren' : 'Aktivieren'}
|
||||||
|
>
|
||||||
|
{area.is_active ? <EyeOff size={14} /> : <Eye size={14} />}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => setEditingId(area.id)}
|
||||||
|
style={{ padding: '6px 12px' }}
|
||||||
|
title="Bearbeiten"
|
||||||
|
>
|
||||||
|
<Pencil size={14} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => handleDelete(area.id)}
|
||||||
|
style={{ padding: '6px 12px', color: '#DC2626' }}
|
||||||
|
title="Löschen"
|
||||||
|
>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{data.areas.length === 0 && (
|
||||||
|
<div style={{
|
||||||
|
padding: 40,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'var(--text3)'
|
||||||
|
}}>
|
||||||
|
Keine Focus Areas vorhanden
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -485,6 +485,23 @@ export default function AdminPanel() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Focus Areas Section */}
|
||||||
|
<div className="card section-gap" style={{marginTop:16}}>
|
||||||
|
<div style={{fontWeight:700,fontSize:14,marginBottom:12,display:'flex',alignItems:'center',gap:6}}>
|
||||||
|
<Settings size={16} color="var(--accent)"/> Focus Areas (v9g)
|
||||||
|
</div>
|
||||||
|
<div style={{fontSize:12,color:'var(--text3)',marginBottom:12,lineHeight:1.5}}>
|
||||||
|
Verwalte Focus Area Definitionen: Dynamisches, erweiterbares System mit 26+ Bereichen über 7 Kategorien.
|
||||||
|
</div>
|
||||||
|
<div style={{display:'grid',gap:8}}>
|
||||||
|
<Link to="/admin/focus-areas">
|
||||||
|
<button className="btn btn-secondary btn-full">
|
||||||
|
🎯 Focus Areas verwalten
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -365,4 +365,13 @@ export const api = {
|
||||||
// Fitness Tests
|
// Fitness Tests
|
||||||
listFitnessTests: () => req('/goals/tests'),
|
listFitnessTests: () => req('/goals/tests'),
|
||||||
createFitnessTest: (d) => req('/goals/tests', json(d)),
|
createFitnessTest: (d) => req('/goals/tests', json(d)),
|
||||||
|
|
||||||
|
// Focus Areas (v2.0)
|
||||||
|
listFocusAreaDefinitions: (includeInactive=false) => req(`/focus-areas/definitions?include_inactive=${includeInactive}`),
|
||||||
|
createFocusAreaDefinition: (d) => req('/focus-areas/definitions', json(d)),
|
||||||
|
updateFocusAreaDefinition: (id,d) => req(`/focus-areas/definitions/${id}`, jput(d)),
|
||||||
|
deleteFocusAreaDefinition: (id) => req(`/focus-areas/definitions/${id}`, {method:'DELETE'}),
|
||||||
|
getUserFocusPreferences: () => req('/focus-areas/user-preferences'),
|
||||||
|
updateUserFocusPreferences: (d) => req('/focus-areas/user-preferences', jput(d)),
|
||||||
|
getFocusAreaStats: () => req('/focus-areas/stats'),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user