diff --git a/backend/reference_value_validation.py b/backend/reference_value_validation.py index f42abbe..5bb1ebf 100644 --- a/backend/reference_value_validation.py +++ b/backend/reference_value_validation.py @@ -37,6 +37,9 @@ REF_VALUE_METHODS = frozenset( REF_VALUE_CONFIDENCE = frozenset({"high", "medium", "low", "unknown"}) +# Anzeigereihenfolge (nicht alphabetisch) +REF_VALUE_CONFIDENCE_ORDER = ("high", "medium", "low", "unknown") + VALUE_DATA_TYPES = frozenset({"integer", "decimal", "percentage", "text", "enum"}) diff --git a/backend/routers/admin_reference_value_types.py b/backend/routers/admin_reference_value_types.py index dad2a15..b99f04c 100644 --- a/backend/routers/admin_reference_value_types.py +++ b/backend/routers/admin_reference_value_types.py @@ -48,7 +48,6 @@ class ReferenceValueTypeAdminCreate(BaseModel): default_unit: Optional[str] = Field(None, max_length=32) value_data_type: str = "decimal" validation_rules: Optional[dict] = None - sort_order: int = 0 active: bool = True metadata: Optional[dict] = None @@ -60,11 +59,14 @@ class ReferenceValueTypeAdminUpdate(BaseModel): default_unit: Optional[str] = Field(None, max_length=32) value_data_type: Optional[str] = None validation_rules: Optional[dict] = None - sort_order: Optional[int] = None active: Optional[bool] = None metadata: Optional[dict] = None +class ReferenceValueTypesReorderBody(BaseModel): + ordered_ids: list[int] = Field(..., min_length=1) + + def _normalize_key(key: str) -> str: k = key.strip().lower() if not KEY_PATTERN.match(k): @@ -106,6 +108,37 @@ def admin_list_reference_value_types(session: dict = Depends(require_admin)): return [_serialize_type(r2d(r)) for r in cur.fetchall()] +@router.post("/reorder") +def admin_reorder_reference_value_types( + body: ReferenceValueTypesReorderBody, + session: dict = Depends(require_admin), +): + """Globale Reihenfolge setzen (sort_order = 10, 20, …). Liste muss alle Typ-IDs genau einmal enthalten.""" + ids = body.ordered_ids + with get_db() as conn: + cur = get_cursor(conn) + cur.execute("SELECT id FROM reference_value_types") + all_ids = sorted([r["id"] for r in cur.fetchall()]) + if len(ids) != len(all_ids): + raise HTTPException( + 400, + f"Erwartet {len(all_ids)} Einträge, erhalten {len(ids)}.", + ) + if sorted(ids) != all_ids: + raise HTTPException(400, "Die ID-Liste muss alle Kennwert-Typen exakt einmal enthalten (keine Duplikate).") + if len(set(ids)) != len(ids): + raise HTTPException(400, "Doppelte IDs sind nicht erlaubt.") + + with get_db() as conn: + cur = get_cursor(conn) + for idx, tid in enumerate(ids): + cur.execute( + "UPDATE reference_value_types SET sort_order = %s WHERE id = %s", + ((idx + 1) * 10, tid), + ) + return {"ok": True} + + @router.get("/{type_id}") def admin_get_reference_value_type(type_id: int, session: dict = Depends(require_admin)): with get_db() as conn: @@ -148,6 +181,8 @@ def admin_create_reference_value_type( with get_db() as conn: cur = get_cursor(conn) try: + cur.execute("SELECT COALESCE(MAX(sort_order), 0) AS m FROM reference_value_types") + next_sort = int(cur.fetchone()["m"]) + 10 cur.execute( """ INSERT INTO reference_value_types @@ -166,7 +201,7 @@ def admin_create_reference_value_type( du, vdt, Json(rules), - body.sort_order, + next_sort, body.active, Json(meta), ), diff --git a/backend/routers/reference_values.py b/backend/routers/reference_values.py index 9fb0efd..2709f3b 100644 --- a/backend/routers/reference_values.py +++ b/backend/routers/reference_values.py @@ -18,6 +18,7 @@ from auth import require_auth from db import get_db, get_cursor, r2d from reference_value_validation import ( REF_VALUE_CONFIDENCE, + REF_VALUE_CONFIDENCE_ORDER, REF_VALUE_METHODS, REF_VALUE_SOURCES, validate_meta_confidence, @@ -112,7 +113,7 @@ def list_reference_value_meta_enums(session: dict = Depends(require_auth)): return { "sources": sorted(REF_VALUE_SOURCES), "methods": sorted(REF_VALUE_METHODS), - "confidence_levels": sorted(REF_VALUE_CONFIDENCE), + "confidence_levels": [x for x in REF_VALUE_CONFIDENCE_ORDER if x in REF_VALUE_CONFIDENCE], } diff --git a/frontend/src/pages/AdminReferenceValueTypesPage.jsx b/frontend/src/pages/AdminReferenceValueTypesPage.jsx index 39b1114..ab631f4 100644 --- a/frontend/src/pages/AdminReferenceValueTypesPage.jsx +++ b/frontend/src/pages/AdminReferenceValueTypesPage.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react' import { Link } from 'react-router-dom' -import { Gauge, Plus, Pencil, Trash2, Save, X } from 'lucide-react' +import { Gauge, Plus, Pencil, Trash2, Save, X, ChevronUp, ChevronDown } from 'lucide-react' import { api } from '../utils/api' import { VALUE_DATA_TYPE_LABELS } from '../utils/referenceValueMeta' @@ -47,7 +47,6 @@ const emptyForm = () => ({ vr_max_length: '', vr_not_empty: true, vr_enum_list: '', - sort_order: 0, active: true, metadata_json: '{}', }) @@ -55,6 +54,7 @@ const emptyForm = () => ({ export default function AdminReferenceValueTypesPage() { const [rows, setRows] = useState([]) const [loading, setLoading] = useState(true) + const [reorderBusy, setReorderBusy] = useState(false) const [error, setError] = useState(null) const [toast, setToast] = useState(null) const [editingId, setEditingId] = useState(null) @@ -106,7 +106,6 @@ export default function AdminReferenceValueTypesPage() { vr_max_length: vr.max_length != null ? String(vr.max_length) : '', vr_not_empty: vr.not_empty !== false, vr_enum_list: allowed, - sort_order: r.sort_order ?? 0, active: !!r.active, metadata_json: r.metadata && typeof r.metadata === 'object' ? JSON.stringify(r.metadata, null, 2) : '{}', }) @@ -155,7 +154,6 @@ export default function AdminReferenceValueTypesPage() { default_unit: form.default_unit.trim(), value_data_type: form.value_data_type, validation_rules, - sort_order: Number(form.sort_order) || 0, active: !!form.active, metadata, } @@ -201,6 +199,24 @@ export default function AdminReferenceValueTypesPage() { } } + const moveRow = async (index, dir) => { + const j = dir === 'up' ? index - 1 : index + 1 + if (j < 0 || j >= rows.length) return + setReorderBusy(true) + setError(null) + const next = [...rows] + ;[next[index], next[j]] = [next[j], next[index]] + try { + await api.adminReorderReferenceValueTypes(next.map((r) => r.id)) + setRows(next) + } catch (e) { + setError(e.message || 'Reihenfolge konnte nicht gespeichert werden') + await load() + } finally { + setReorderBusy(false) + } + } + const plausibilisierungBlock = () => { const t = form.value_data_type if (t === 'integer' || t === 'decimal' || t === 'percentage') { @@ -450,18 +466,6 @@ export default function AdminReferenceValueTypesPage() { placeholder="bpm, %, Stufe, …" /> -
- - setForm((f) => ({ ...f, sort_order: e.target.value }))} - /> -
Sichtbarkeit