fix: Stilrichtungen → Fokusbereich Zuordnung (focus_area_id)
Problem: Stilrichtungen-Tab zeigte falsches Feld 'Übergeordneter Stil' (parent_style_id) Lösung: Fokusbereich-Dropdown (focus_area_id) für Hierarchie Backend (catalogs.py): - INSERT: focus_area_id Parameter hinzugefügt - UPDATE: focus_area_id Parameter hinzugefügt - SELECT: LEFT JOIN focus_areas für focus_area_name + icon - Response enriched mit focus_area_name/icon Frontend (AdminCatalogsPage.jsx): - newTS State: parent_style_id → focus_area_id - Create Form: Fokusbereich-Dropdown statt 'Übergeordneter Stil' - Edit Form: Fokusbereich-Dropdown mit focusAreas - Display: Zeigt Fokusbereich-Icon + Name (accent color) - parent_style_id bleibt für optionale Stil-Hierarchien Jetzt kann Hierarchie korrekt angelegt werden: ✅ Karate → Goju-Ryu ✅ Karate → Shotokan ✅ Gewaltschutz → (eigene Stilrichtungen) version: 0.5.0 module: catalogs 1.5.0, AdminCatalogsPage 2.2.0 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
377f365473
commit
9f44cff77b
|
|
@ -141,9 +141,10 @@ def list_training_styles(
|
||||||
cur = get_cursor(conn)
|
cur = get_cursor(conn)
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
SELECT ts.*, ps.name as parent_style_name
|
SELECT ts.*, ps.name as parent_style_name, fa.name as focus_area_name, fa.icon as focus_area_icon
|
||||||
FROM style_directions ts
|
FROM style_directions ts
|
||||||
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
|
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
|
||||||
|
LEFT JOIN focus_areas fa ON ts.focus_area_id = fa.id
|
||||||
"""
|
"""
|
||||||
params = []
|
params = []
|
||||||
|
|
||||||
|
|
@ -173,13 +174,14 @@ def create_training_style(data: dict, session=Depends(require_auth)):
|
||||||
cur = get_cursor(conn)
|
cur = get_cursor(conn)
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO style_directions (name, abbreviation, description, parent_style_id, sort_order, status)
|
INSERT INTO style_directions (name, abbreviation, description, focus_area_id, parent_style_id, sort_order, status)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""", (
|
""", (
|
||||||
name,
|
name,
|
||||||
data.get('abbreviation'),
|
data.get('abbreviation'),
|
||||||
data.get('description'),
|
data.get('description'),
|
||||||
|
data.get('focus_area_id'),
|
||||||
data.get('parent_style_id'),
|
data.get('parent_style_id'),
|
||||||
data.get('sort_order', 99),
|
data.get('sort_order', 99),
|
||||||
data.get('status', 'active')
|
data.get('status', 'active')
|
||||||
|
|
@ -189,9 +191,10 @@ def create_training_style(data: dict, session=Depends(require_auth)):
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT ts.*, ps.name as parent_style_name
|
SELECT ts.*, ps.name as parent_style_name, fa.name as focus_area_name, fa.icon as focus_area_icon
|
||||||
FROM style_directions ts
|
FROM style_directions ts
|
||||||
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
|
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
|
||||||
|
LEFT JOIN focus_areas fa ON ts.focus_area_id = fa.id
|
||||||
WHERE ts.id = %s
|
WHERE ts.id = %s
|
||||||
""", (style_id,))
|
""", (style_id,))
|
||||||
return r2d(cur.fetchone())
|
return r2d(cur.fetchone())
|
||||||
|
|
@ -212,6 +215,7 @@ def update_training_style(style_id: int, data: dict, session=Depends(require_aut
|
||||||
name = %s,
|
name = %s,
|
||||||
abbreviation = %s,
|
abbreviation = %s,
|
||||||
description = %s,
|
description = %s,
|
||||||
|
focus_area_id = %s,
|
||||||
parent_style_id = %s,
|
parent_style_id = %s,
|
||||||
sort_order = %s,
|
sort_order = %s,
|
||||||
status = %s,
|
status = %s,
|
||||||
|
|
@ -221,6 +225,7 @@ def update_training_style(style_id: int, data: dict, session=Depends(require_aut
|
||||||
data.get('name'),
|
data.get('name'),
|
||||||
data.get('abbreviation'),
|
data.get('abbreviation'),
|
||||||
data.get('description'),
|
data.get('description'),
|
||||||
|
data.get('focus_area_id'),
|
||||||
data.get('parent_style_id'),
|
data.get('parent_style_id'),
|
||||||
data.get('sort_order'),
|
data.get('sort_order'),
|
||||||
data.get('status'),
|
data.get('status'),
|
||||||
|
|
@ -230,9 +235,10 @@ def update_training_style(style_id: int, data: dict, session=Depends(require_aut
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT ts.*, ps.name as parent_style_name
|
SELECT ts.*, ps.name as parent_style_name, fa.name as focus_area_name, fa.icon as focus_area_icon
|
||||||
FROM style_directions ts
|
FROM style_directions ts
|
||||||
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
|
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
|
||||||
|
LEFT JOIN focus_areas fa ON ts.focus_area_id = fa.id
|
||||||
WHERE ts.id = %s
|
WHERE ts.id = %s
|
||||||
""", (style_id,))
|
""", (style_id,))
|
||||||
return r2d(cur.fetchone())
|
return r2d(cur.fetchone())
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ export default function AdminCatalogsPage() {
|
||||||
const [editingFA, setEditingFA] = useState(null)
|
const [editingFA, setEditingFA] = useState(null)
|
||||||
const [newFA, setNewFA] = useState({ name: '', description: '', color: '#1D9E75', icon: '' })
|
const [newFA, setNewFA] = useState({ name: '', description: '', color: '#1D9E75', icon: '' })
|
||||||
|
|
||||||
// Training Styles
|
// Style Directions (Stilrichtungen)
|
||||||
const [trainingStyles, setTrainingStyles] = useState([])
|
const [trainingStyles, setTrainingStyles] = useState([])
|
||||||
const [editingTS, setEditingTS] = useState(null)
|
const [editingTS, setEditingTS] = useState(null)
|
||||||
const [newTS, setNewTS] = useState({ name: '', description: '', parent_style_id: null })
|
const [newTS, setNewTS] = useState({ name: '', description: '', focus_area_id: null })
|
||||||
|
|
||||||
// Training Characters
|
// Training Characters
|
||||||
const [trainingCharacters, setTrainingCharacters] = useState([])
|
const [trainingCharacters, setTrainingCharacters] = useState([])
|
||||||
|
|
@ -139,7 +139,7 @@ export default function AdminCatalogsPage() {
|
||||||
async function createStyleDirection() {
|
async function createStyleDirection() {
|
||||||
try {
|
try {
|
||||||
await api.createStyleDirection(newTS)
|
await api.createStyleDirection(newTS)
|
||||||
setNewTS({ name: '', description: '', parent_style_id: null })
|
setNewTS({ name: '', description: '', focus_area_id: null })
|
||||||
loadData()
|
loadData()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message)
|
setError(e.message)
|
||||||
|
|
@ -497,15 +497,15 @@ export default function AdminCatalogsPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Übergeordneter Stil (optional)</label>
|
<label className="form-label">Fokusbereich</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={newTS.parent_style_id || ''}
|
value={newTS.focus_area_id || ''}
|
||||||
onChange={e => setNewTS({ ...newTS, parent_style_id: e.target.value ? parseInt(e.target.value) : null })}
|
onChange={e => setNewTS({ ...newTS, focus_area_id: e.target.value ? parseInt(e.target.value) : null })}
|
||||||
>
|
>
|
||||||
<option value="">- Kein -</option>
|
<option value="">- Fokusbereich auswählen -</option>
|
||||||
{trainingStyles.map(ts => (
|
{focusAreas.map(fa => (
|
||||||
<option key={ts.id} value={ts.id}>{ts.name}</option>
|
<option key={fa.id} value={fa.id}>{fa.icon} {fa.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -535,15 +535,15 @@ export default function AdminCatalogsPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Übergeordneter Stil</label>
|
<label className="form-label">Fokusbereich</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={editingTS.parent_style_id || ''}
|
value={editingTS.focus_area_id || ''}
|
||||||
onChange={e => setEditingTS({ ...editingTS, parent_style_id: e.target.value ? parseInt(e.target.value) : null })}
|
onChange={e => setEditingTS({ ...editingTS, focus_area_id: e.target.value ? parseInt(e.target.value) : null })}
|
||||||
>
|
>
|
||||||
<option value="">- Kein -</option>
|
<option value="">- Fokusbereich auswählen -</option>
|
||||||
{trainingStyles.filter(x => x.id !== ts.id).map(x => (
|
{focusAreas.map(fa => (
|
||||||
<option key={x.id} value={x.id}>{x.name}</option>
|
<option key={fa.id} value={fa.id}>{fa.icon} {fa.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -555,9 +555,14 @@ export default function AdminCatalogsPage() {
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<h3>{ts.name}</h3>
|
<h3>{ts.name}</h3>
|
||||||
|
{ts.focus_area_name && (
|
||||||
|
<p style={{ margin: '4px 0', color: 'var(--accent)', fontSize: '14px', fontWeight: 500 }}>
|
||||||
|
{ts.focus_area_icon} {ts.focus_area_name}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{ts.parent_style_name && (
|
{ts.parent_style_name && (
|
||||||
<p style={{ margin: '4px 0', color: 'var(--text2)', fontSize: '14px' }}>
|
<p style={{ margin: '4px 0', color: 'var(--text2)', fontSize: '14px' }}>
|
||||||
→ {ts.parent_style_name}
|
→ Untergeordnet: {ts.parent_style_name}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p style={{ margin: '8px 0', color: 'var(--text2)' }}>{ts.description}</p>
|
<p style={{ margin: '8px 0', color: 'var(--text2)' }}>{ts.description}</p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user