feat: Enhance goal progress tracking and display
- Added a function to calculate goal progress percentage based on start, target, and current values. - Updated GoalsPage to display progress in a user-friendly format, including visual progress bars. - Implemented error handling for goal progress updates in the backend to ensure robustness.
This commit is contained in:
parent
df8e732709
commit
1b01f5e6d0
|
|
@ -699,6 +699,12 @@ def get_goals_grouped(session: dict = Depends(require_auth)):
|
|||
grouped[cat] = []
|
||||
|
||||
goal_dict = r2d(goal)
|
||||
# Keep in sync with GET /goals/list: derive current_value & progress from live data
|
||||
try:
|
||||
_update_goal_progress(conn, pid, goal_dict)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to update progress for goal {goal_dict.get('id')}: {e}")
|
||||
|
||||
goal_dict['focus_contributions'] = focus_map.get(goal['id'], [])
|
||||
grouped[cat].append(goal_dict)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,28 @@ const getCategoryForGoalType = (goalType) => {
|
|||
return mapping[goalType] || 'other'
|
||||
}
|
||||
|
||||
/** Fortschritt in % von Start zum Ziel; bevorzugt `progress_pct` vom Server, sonst aus Ist-Werten. */
|
||||
function getGoalProgressPercent(goal) {
|
||||
const apiVal = goal?.progress_pct
|
||||
if (apiVal !== null && apiVal !== undefined && Number.isFinite(Number(apiVal))) {
|
||||
return Number(apiVal)
|
||||
}
|
||||
const s = goal?.start_value != null ? Number(goal.start_value) : NaN
|
||||
const t = goal?.target_value != null ? Number(goal.target_value) : NaN
|
||||
const c =
|
||||
goal?.current_value != null && goal.current_value !== ''
|
||||
? Number(goal.current_value)
|
||||
: NaN
|
||||
if (!Number.isFinite(s) || !Number.isFinite(t) || !Number.isFinite(c)) return null
|
||||
const span = Math.abs(t - s)
|
||||
if (span < 1e-9) {
|
||||
if (Math.abs(c - s) < 1e-9) return 100
|
||||
return null
|
||||
}
|
||||
if (t > s) return ((c - s) / (t - s)) * 100
|
||||
return ((s - c) / (s - t)) * 100
|
||||
}
|
||||
|
||||
export default function GoalsPage() {
|
||||
const [goalMode, setGoalMode] = useState(null)
|
||||
const [userFocusWeights, setUserFocusWeights] = useState([]) // v2.0: User's focus area weights
|
||||
|
|
@ -613,6 +635,17 @@ export default function GoalsPage() {
|
|||
{categoryGoals.map(goal => {
|
||||
const typeInfo = goalTypesMap[goal.goal_type] || { label_de: goal.goal_type, unit: '', icon: '📊' }
|
||||
const priorityInfo = PRIORITY_LEVELS[goal.priority] || PRIORITY_LEVELS[2]
|
||||
const progressPct = getGoalProgressPercent(goal)
|
||||
const progressLabel =
|
||||
progressPct != null
|
||||
? (Math.abs(progressPct - Math.round(progressPct)) < 0.05
|
||||
? `${Math.round(progressPct)}`
|
||||
: `${Math.round(progressPct * 10) / 10}`.replace('.', ','))
|
||||
: null
|
||||
const progressBarWidth =
|
||||
progressPct != null
|
||||
? Math.min(100, Math.max(0, progressPct))
|
||||
: 0
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -719,30 +752,40 @@ export default function GoalsPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{goal.progress_pct !== null && (
|
||||
<div>
|
||||
{progressPct != null && (
|
||||
<div style={{ marginTop: 4 }}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: 12,
|
||||
marginBottom: 4,
|
||||
color: 'var(--text2)'
|
||||
marginBottom: 6,
|
||||
color: 'var(--text2)',
|
||||
alignItems: 'baseline',
|
||||
gap: 8
|
||||
}}>
|
||||
<span>Fortschritt</span>
|
||||
<span style={{ fontWeight: 600, color: 'var(--text1)' }}>{goal.progress_pct}%</span>
|
||||
<span>Fortschritt (Start → Ziel)</span>
|
||||
<span style={{ fontWeight: 600, color: 'var(--text1)' }}>
|
||||
{progressLabel}%
|
||||
{progressPct > 100 && (
|
||||
<span style={{ fontWeight: 500, color: 'var(--text3)', marginLeft: 4 }}>
|
||||
(über Ziel)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: 8,
|
||||
height: 10,
|
||||
background: 'var(--surface)',
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden'
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid var(--border)'
|
||||
}}>
|
||||
<div style={{
|
||||
width: `${Math.min(100, Math.max(0, goal.progress_pct))}%`,
|
||||
width: `${progressBarWidth}%`,
|
||||
height: '100%',
|
||||
background: catInfo.color,
|
||||
transition: 'width 0.3s ease'
|
||||
background: getProgressColor(Math.min(100, Math.max(0, progressPct))),
|
||||
transition: 'width 0.35s ease'
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user