feat: Add profile reference values summary endpoint and UI enhancements
- Introduced a new API endpoint for fetching a summary of profile reference values, providing the latest and previous entries for each reference type. - Updated ProfileReferenceValuesPage to display summary tiles with trend indicators for better user insights. - Enhanced CSS for responsive layout of reference value tiles, improving the overall user experience on different screen sizes. - Implemented trend calculation logic to visually represent changes between the latest and previous reference values.
This commit is contained in:
parent
c152721fe8
commit
3e916c082c
|
|
@ -117,6 +117,83 @@ def list_reference_value_meta_enums(session: dict = Depends(require_auth)):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/profile-reference-values/summary")
|
||||||
|
def profile_reference_values_summary(
|
||||||
|
x_profile_id: Optional[str] = Header(default=None),
|
||||||
|
session: dict = Depends(require_auth),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Für das aktive Profil: je Referenztyp mit mindestens einem Eintrag der jüngste Wert
|
||||||
|
plus der unmittelbar vorherige (gleiche Sortierung wie Liste), für Tendenz-Anzeigen.
|
||||||
|
"""
|
||||||
|
pid = get_pid(x_profile_id)
|
||||||
|
with get_db() as conn:
|
||||||
|
cur = get_cursor(conn)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
WITH ranked AS (
|
||||||
|
SELECT
|
||||||
|
v.id,
|
||||||
|
v.profile_id,
|
||||||
|
v.reference_value_type_id,
|
||||||
|
v.effective_date,
|
||||||
|
v.value_numeric,
|
||||||
|
v.value_text,
|
||||||
|
v.unit,
|
||||||
|
v.source,
|
||||||
|
v.confidence,
|
||||||
|
v.method,
|
||||||
|
v.notes,
|
||||||
|
v.extra,
|
||||||
|
v.created_at,
|
||||||
|
v.updated_at,
|
||||||
|
rt.key AS type_key,
|
||||||
|
rt.label AS type_label,
|
||||||
|
rt.sort_order AS type_sort_order,
|
||||||
|
rt.value_data_type,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY v.reference_value_type_id
|
||||||
|
ORDER BY v.effective_date DESC, v.created_at DESC
|
||||||
|
) AS rn
|
||||||
|
FROM profile_reference_values v
|
||||||
|
JOIN reference_value_types rt ON rt.id = v.reference_value_type_id
|
||||||
|
WHERE v.profile_id = %s AND rt.active = TRUE
|
||||||
|
)
|
||||||
|
SELECT * FROM ranked WHERE rn <= 2
|
||||||
|
ORDER BY type_sort_order ASC, type_key ASC, rn ASC
|
||||||
|
""",
|
||||||
|
(pid,),
|
||||||
|
)
|
||||||
|
raw_rows = [r2d(r) for r in cur.fetchall()]
|
||||||
|
|
||||||
|
by_key: dict[str, dict[str, Any]] = {}
|
||||||
|
skip_cols = frozenset({"rn", "type_sort_order", "value_data_type"})
|
||||||
|
for row in raw_rows:
|
||||||
|
rn = int(row.get("rn") or 0)
|
||||||
|
key = row["type_key"]
|
||||||
|
if key not in by_key:
|
||||||
|
by_key[key] = {
|
||||||
|
"type_key": key,
|
||||||
|
"type_label": row.get("type_label") or key,
|
||||||
|
"value_data_type": (row.get("value_data_type") or "decimal").strip().lower(),
|
||||||
|
"sort_key": (row.get("type_sort_order") or 0, key),
|
||||||
|
"latest": None,
|
||||||
|
"previous": None,
|
||||||
|
}
|
||||||
|
entry = {k: v for k, v in row.items() if k not in skip_cols}
|
||||||
|
api_entry = _row_to_api(entry)
|
||||||
|
if rn == 1:
|
||||||
|
by_key[key]["latest"] = api_entry
|
||||||
|
elif rn == 2:
|
||||||
|
by_key[key]["previous"] = api_entry
|
||||||
|
|
||||||
|
tiles = sorted(by_key.values(), key=lambda t: t["sort_key"])
|
||||||
|
for t in tiles:
|
||||||
|
t.pop("sort_key", None)
|
||||||
|
|
||||||
|
return {"tiles": tiles}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/profile-reference-values")
|
@router.get("/profile-reference-values")
|
||||||
def list_profile_reference_values(
|
def list_profile_reference_values(
|
||||||
type_key: str = Query(..., description="Schlüssel aus reference_value_types.key"),
|
type_key: str = Query(..., description="Schlüssel aus reference_value_types.key"),
|
||||||
|
|
|
||||||
|
|
@ -557,6 +557,61 @@ a.analysis-split__nav-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Referenzwerte: Übersichtskacheln (responsive, bis 4 Spalten Desktop) */
|
||||||
|
.ref-value-tiles-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 520px) {
|
||||||
|
.ref-value-tiles-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.ref-value-tiles-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.ref-value-tiles-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-value-tile {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 14px 14px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-family: var(--font);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1.5px solid var(--border2);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text1);
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-value-tile:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-value-tile:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-value-tile--active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--surface2);
|
||||||
|
}
|
||||||
|
|
||||||
/* Admin: Split-Layout wie .analysis-split (nur Gruppen in der Nav) */
|
/* Admin: Split-Layout wie .analysis-split (nur Gruppen in der Nav) */
|
||||||
.admin-shell {
|
.admin-shell {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { Gauge, Pencil, Trash2, Plus } from 'lucide-react'
|
import {
|
||||||
|
Gauge,
|
||||||
|
Pencil,
|
||||||
|
Trash2,
|
||||||
|
Plus,
|
||||||
|
TrendingUp,
|
||||||
|
TrendingDown,
|
||||||
|
Minus,
|
||||||
|
ArrowLeftRight,
|
||||||
|
} from 'lucide-react'
|
||||||
import { api } from '../utils/api'
|
import { api } from '../utils/api'
|
||||||
import {
|
import {
|
||||||
labelSource,
|
labelSource,
|
||||||
|
|
@ -45,6 +54,55 @@ function buildValuePayload(selectedType, rawStr) {
|
||||||
return { error: null, value_numeric: null, value_text: s }
|
return { error: null, value_numeric: null, value_text: s }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatNumericDelta(d, vdt) {
|
||||||
|
const v = (vdt || 'decimal').toLowerCase()
|
||||||
|
const sign = d > 0 ? '+' : '−'
|
||||||
|
let abs = Math.abs(d)
|
||||||
|
if (v === 'integer') {
|
||||||
|
return `${sign}${Math.round(abs)}`
|
||||||
|
}
|
||||||
|
let s = abs.toFixed(3)
|
||||||
|
s = s.replace(/(\.\d*?[1-9])0+$/, '$1').replace(/\.$/, '')
|
||||||
|
return `${sign}${s}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tendenz: jüngster vs. vorheriger Eintrag (gleiche Sortierung wie API). */
|
||||||
|
function computeRefValueTrend(valueDataType, latest, previous) {
|
||||||
|
if (!latest || !previous) {
|
||||||
|
return { variant: 'none', label: null, Icon: null }
|
||||||
|
}
|
||||||
|
const vdt = (valueDataType || 'decimal').toLowerCase()
|
||||||
|
const numericTypes = ['integer', 'decimal', 'percentage']
|
||||||
|
if (numericTypes.includes(vdt)) {
|
||||||
|
const a = latest.value_numeric != null ? Number(latest.value_numeric) : NaN
|
||||||
|
const b = previous.value_numeric != null ? Number(previous.value_numeric) : NaN
|
||||||
|
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
||||||
|
return { variant: 'unknown', label: 'Tendenz n/v', Icon: Minus }
|
||||||
|
}
|
||||||
|
const d = a - b
|
||||||
|
if (Math.abs(d) < 1e-9) {
|
||||||
|
return { variant: 'flat', label: 'gleich', Icon: Minus }
|
||||||
|
}
|
||||||
|
if (d > 0) {
|
||||||
|
return { variant: 'up', label: formatNumericDelta(d, vdt), Icon: TrendingUp }
|
||||||
|
}
|
||||||
|
return { variant: 'down', label: formatNumericDelta(d, vdt), Icon: TrendingDown }
|
||||||
|
}
|
||||||
|
const sa = formatEntryValue(latest)
|
||||||
|
const sb = formatEntryValue(previous)
|
||||||
|
if (sa === sb) {
|
||||||
|
return { variant: 'flat', label: 'unverändert', Icon: Minus }
|
||||||
|
}
|
||||||
|
return { variant: 'changed', label: 'geändert', Icon: ArrowLeftRight }
|
||||||
|
}
|
||||||
|
|
||||||
|
function trendAccent(variant) {
|
||||||
|
if (variant === 'up') return 'var(--accent)'
|
||||||
|
if (variant === 'down') return 'var(--danger)'
|
||||||
|
if (variant === 'changed') return '#B45309'
|
||||||
|
return 'var(--text3)'
|
||||||
|
}
|
||||||
|
|
||||||
export default function ProfileReferenceValuesPage() {
|
export default function ProfileReferenceValuesPage() {
|
||||||
const [types, setTypes] = useState([])
|
const [types, setTypes] = useState([])
|
||||||
const [metaEnums, setMetaEnums] = useState({ sources: [], methods: [], confidence_levels: [] })
|
const [metaEnums, setMetaEnums] = useState({ sources: [], methods: [], confidence_levels: [] })
|
||||||
|
|
@ -52,6 +110,7 @@ export default function ProfileReferenceValuesPage() {
|
||||||
const [entries, setEntries] = useState([])
|
const [entries, setEntries] = useState([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [listLoading, setListLoading] = useState(false)
|
const [listLoading, setListLoading] = useState(false)
|
||||||
|
const [summaryTiles, setSummaryTiles] = useState([])
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [editingId, setEditingId] = useState(null)
|
const [editingId, setEditingId] = useState(null)
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
|
|
@ -66,11 +125,13 @@ export default function ProfileReferenceValuesPage() {
|
||||||
const loadTypes = useCallback(async () => {
|
const loadTypes = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const [data, enums] = await Promise.all([
|
const [data, enums, summaryRes] = await Promise.all([
|
||||||
api.listReferenceValueTypes(),
|
api.listReferenceValueTypes(),
|
||||||
api.listReferenceValueMetaEnums(),
|
api.listReferenceValueMetaEnums(),
|
||||||
|
api.listProfileReferenceValuesSummary().catch(() => ({ tiles: [] })),
|
||||||
])
|
])
|
||||||
setTypes(Array.isArray(data) ? data : [])
|
setTypes(Array.isArray(data) ? data : [])
|
||||||
|
setSummaryTiles(Array.isArray(summaryRes?.tiles) ? summaryRes.tiles : [])
|
||||||
setMetaEnums(
|
setMetaEnums(
|
||||||
enums && typeof enums === 'object'
|
enums && typeof enums === 'object'
|
||||||
? enums
|
? enums
|
||||||
|
|
@ -80,11 +141,21 @@ export default function ProfileReferenceValuesPage() {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message || 'Typen konnten nicht geladen werden')
|
setError(e.message || 'Typen konnten nicht geladen werden')
|
||||||
setTypes([])
|
setTypes([])
|
||||||
|
setSummaryTiles([])
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const loadSummaryOnly = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const summaryRes = await api.listProfileReferenceValuesSummary()
|
||||||
|
setSummaryTiles(Array.isArray(summaryRes?.tiles) ? summaryRes.tiles : [])
|
||||||
|
} catch {
|
||||||
|
setSummaryTiles([])
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTypes()
|
loadTypes()
|
||||||
}, [loadTypes])
|
}, [loadTypes])
|
||||||
|
|
@ -125,9 +196,6 @@ export default function ProfileReferenceValuesPage() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestEntry =
|
|
||||||
!listLoading && entries.length > 0 ? entries[0] : null
|
|
||||||
|
|
||||||
const rules = selectedType?.validation_rules && typeof selectedType.validation_rules === 'object'
|
const rules = selectedType?.validation_rules && typeof selectedType.validation_rules === 'object'
|
||||||
? selectedType.validation_rules
|
? selectedType.validation_rules
|
||||||
: {}
|
: {}
|
||||||
|
|
@ -260,7 +328,7 @@ export default function ProfileReferenceValuesPage() {
|
||||||
await api.createProfileReferenceValue(payload)
|
await api.createProfileReferenceValue(payload)
|
||||||
}
|
}
|
||||||
resetForm()
|
resetForm()
|
||||||
await loadEntries()
|
await Promise.all([loadEntries(), loadSummaryOnly()])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || 'Speichern fehlgeschlagen')
|
setError(err.message || 'Speichern fehlgeschlagen')
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +352,7 @@ export default function ProfileReferenceValuesPage() {
|
||||||
setError(null)
|
setError(null)
|
||||||
await api.deleteProfileReferenceValue(id)
|
await api.deleteProfileReferenceValue(id)
|
||||||
if (editingId === id) resetForm()
|
if (editingId === id) resetForm()
|
||||||
await loadEntries()
|
await Promise.all([loadEntries(), loadSummaryOnly()])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || 'Löschen fehlgeschlagen')
|
setError(err.message || 'Löschen fehlgeschlagen')
|
||||||
}
|
}
|
||||||
|
|
@ -332,6 +400,94 @@ export default function ProfileReferenceValuesPage() {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{summaryTiles.filter((t) => t?.latest).length > 0 && (
|
||||||
|
<div className="card section-gap">
|
||||||
|
<div className="card-title">Aktuelle Werte</div>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--text2)', marginTop: 4, marginBottom: 14, lineHeight: 1.5 }}>
|
||||||
|
Übersicht aller Kennwerte mit gespeicherten Einträgen – Kachel antippen, um den Typ unten zu wählen.
|
||||||
|
Tendenz bezieht sich auf den Vergleich mit dem vorherigen Eintrag.
|
||||||
|
</p>
|
||||||
|
<div className="ref-value-tiles-grid">
|
||||||
|
{summaryTiles
|
||||||
|
.filter((t) => t?.latest)
|
||||||
|
.map((tile) => {
|
||||||
|
const latest = tile.latest
|
||||||
|
const trend = computeRefValueTrend(tile.value_data_type, latest, tile.previous)
|
||||||
|
const TrendIcon = trend.Icon
|
||||||
|
const active = tile.type_key === selectedKey
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tile.type_key}
|
||||||
|
type="button"
|
||||||
|
className={'ref-value-tile' + (active ? ' ref-value-tile--active' : '')}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedKey(tile.type_key)
|
||||||
|
setEditingId(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--text2)',
|
||||||
|
marginBottom: 6,
|
||||||
|
lineHeight: 1.3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tile.type_label}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--text1)',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
letterSpacing: '-0.02em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatEntryValue(latest)}
|
||||||
|
{latest.unit ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--text2)',
|
||||||
|
marginLeft: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{latest.unit}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 6 }}>
|
||||||
|
Stand {String(latest.effective_date || '').slice(0, 10)}
|
||||||
|
</div>
|
||||||
|
{trend.variant !== 'none' && TrendIcon ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
color: trendAccent(trend.variant),
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrendIcon size={15} strokeWidth={2.25} aria-hidden />
|
||||||
|
<span>
|
||||||
|
{trend.label}
|
||||||
|
<span style={{ color: 'var(--text3)', fontWeight: 400 }}> · ggü. Vorwert</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="card section-gap">
|
<div className="card section-gap">
|
||||||
<div className="card-title">Referenztyp</div>
|
<div className="card-title">Referenztyp</div>
|
||||||
<div className="settings-page__field" style={{ borderBottom: 'none', paddingTop: 0 }}>
|
<div className="settings-page__field" style={{ borderBottom: 'none', paddingTop: 0 }}>
|
||||||
|
|
@ -373,44 +529,6 @@ export default function ProfileReferenceValuesPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{latestEntry && selectedType && (
|
|
||||||
<div
|
|
||||||
className="card section-gap"
|
|
||||||
style={{
|
|
||||||
borderLeft: '4px solid var(--accent)',
|
|
||||||
background: 'var(--surface)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="card-title" style={{ marginBottom: 8 }}>
|
|
||||||
Aktueller Wert
|
|
||||||
</div>
|
|
||||||
<p style={{ fontSize: 12, color: 'var(--text3)', margin: '0 0 10px', lineHeight: 1.4 }}>
|
|
||||||
Stand {String(latestEntry.effective_date || '').slice(0, 10)} · zuletzt erfasst für{' '}
|
|
||||||
<strong>{selectedType.label}</strong>
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: 'var(--text1)',
|
|
||||||
lineHeight: 1.2,
|
|
||||||
letterSpacing: '-0.02em',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatEntryValue(latestEntry)}
|
|
||||||
{latestEntry.unit ? (
|
|
||||||
<span style={{ fontSize: 16, fontWeight: 600, color: 'var(--text2)', marginLeft: 8 }}>
|
|
||||||
{latestEntry.unit}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<p style={{ fontSize: 13, color: 'var(--text2)', margin: '10px 0 0', lineHeight: 1.5 }}>
|
|
||||||
{labelSource(latestEntry.source)} · {labelMethod(latestEntry.method)} ·{' '}
|
|
||||||
{labelConfidence(latestEntry.confidence)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="card section-gap">
|
<div className="card section-gap">
|
||||||
<div className="card-title" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className="card-title" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ export const api = {
|
||||||
listReferenceValueMetaEnums: () => req('/reference-value-meta/enums'),
|
listReferenceValueMetaEnums: () => req('/reference-value-meta/enums'),
|
||||||
listProfileReferenceValues: (typeKey) =>
|
listProfileReferenceValues: (typeKey) =>
|
||||||
req(`/profile-reference-values?type_key=${encodeURIComponent(typeKey)}`),
|
req(`/profile-reference-values?type_key=${encodeURIComponent(typeKey)}`),
|
||||||
|
listProfileReferenceValuesSummary: () => req('/profile-reference-values/summary'),
|
||||||
createProfileReferenceValue: (d) => req('/profile-reference-values', json(d)),
|
createProfileReferenceValue: (d) => req('/profile-reference-values', json(d)),
|
||||||
updateProfileReferenceValue: (id, d) => req(`/profile-reference-values/${id}`, jput(d)),
|
updateProfileReferenceValue: (id, d) => req(`/profile-reference-values/${id}`, jput(d)),
|
||||||
deleteProfileReferenceValue: (id) => req(`/profile-reference-values/${id}`, { method: 'DELETE' }),
|
deleteProfileReferenceValue: (id) => req(`/profile-reference-values/${id}`, { method: 'DELETE' }),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user