// Thin fetch wrapper. Redirects to login on 401. async function req(method, url, body) { const opts = { method, headers: {} }; if (body !== undefined) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); } const res = await fetch(url, opts); if (res.status === 401 && !url.endsWith('/api/login')) { location.href = '/login.html'; throw new Error('Not authenticated'); } const data = res.headers.get('content-type')?.includes('application/json') ? await res.json() : null; if (!res.ok) throw new Error((data && data.error) || `Request failed (${res.status})`); return data; } export const api = { login: (password) => req('POST', '/api/login', { password }), logout: () => req('POST', '/api/logout'), me: () => req('GET', '/api/me'), setPassword: (current, next) => req('POST', '/api/password', { current, next }), categories: (all = false) => req('GET', `/api/categories${all ? '?all=1' : ''}`), addCategory: (c) => req('POST', '/api/categories', c), updateCategory: (id, c) => req('PUT', `/api/categories/${id}`, c), addMetric: (catId, m) => req('POST', `/api/categories/${catId}/metrics`, m), updateMetric: (id, m) => req('PUT', `/api/metrics/${id}`, m), deleteMetric: (id) => req('DELETE', `/api/metrics/${id}`), day: (day) => req('GET', `/api/day/${day}`), logEntry: (e) => req('POST', '/api/entries', e), updateEntry: (id, e) => req('PUT', `/api/entries/${id}`, e), deleteEntry: (id) => req('DELETE', `/api/entries/${id}`), saveNotes: (day, notes) => req('PUT', `/api/day/${day}/notes`, { notes }), plans: (from, to) => req('GET', `/api/plans?from=${from}&to=${to}`), addPlan: (p) => req('POST', '/api/plans', p), deletePlan: (id) => req('DELETE', `/api/plans/${id}`), goals: () => req('GET', '/api/goals'), addGoal: (g) => req('POST', '/api/goals', g), updateGoal: (id, g) => req('PUT', `/api/goals/${id}`, g), deleteGoal: (id) => req('DELETE', `/api/goals/${id}`), stats: () => req('GET', '/api/stats'), };