diff --git a/public/js/goals.js b/public/js/goals.js new file mode 100644 index 0000000..5232b43 --- /dev/null +++ b/public/js/goals.js @@ -0,0 +1,93 @@ +import { state } from './state.js'; +import { ldoc } from './i18n.js'; + +export function tOff(g) { + return Math.round((state.TODAY - new Date(g.start)) / 86400000); +} + +export function o2d(g, i) { + const d = new Date(new Date(g.start).getTime() + i * 86400000); + d.setHours(0,0,0,0); + return d; +} + +export function dTot(g, o) { + return (g.sets[String(o)] || []).reduce((a, b) => a + b.amount, 0); +} + +export function fd(d) { + return d.toLocaleDateString(ldoc(), { weekday: 'short', day: 'numeric', month: 'short' }); +} + +export function fs(d) { + return d.toLocaleDateString(ldoc(), { day: 'numeric', month: 'short' }); +} + +export function editable(g, o) { + const t = tOff(g); + return o === t || o === t - 1; +} + +export function now() { + const n = new Date(); + return String(n.getHours()).padStart(2, '0') + ':' + String(n.getMinutes()).padStart(2, '0'); +} + +export function heuteColor(tdone, daily) { + if (tdone === 0) return 'var(--red)'; + if (tdone >= daily * 1.1) return 'var(--blue)'; + if (tdone >= daily) return 'var(--green)'; + return 'var(--amber)'; +} + +export function isCollapsed(id) { + return state.collapsed[id] !== false; +} + +export function toggleCollapse(id) { + const wasCollapsed = isCollapsed(id); + state.collapsed[id] = !wasCollapsed; + if (wasCollapsed) { + const g = state.goals.find(x => x.id === id); + if (g) state.selDay[id] = tOff(g); + } +} + +export function calc(g) { + const t = tOff(g), tot = g.daily * g.days; + const dr = Math.max(0, g.days - t - 1); + const sd = new Date(g.start); sd.setHours(0,0,0,0); + const end = new Date(sd.getTime() + g.days * 86400000); + let past = 0; + for (let i = 0; i < Math.min(t, g.days); i++) past += dTot(g, i); + const tdone = dTot(g, t), tot2 = past + tdone; + const dl = dr + 1; + const remaining = Math.max(0, tot - past); + const pd = Math.ceil(remaining / Math.max(1, dl)); + const st = Math.max(0, pd - tdone); + const expectedPast = Math.min(t, g.days) * g.daily; + const buf = Math.floor((past - expectedPast) + Math.max(0, tdone - g.daily)); + const deficit = Math.min(0, buf); + const surplus = Math.max(0, buf); + const dailyDelta = pd - g.daily; + const pct = Math.min(100, Math.round((tot2 / tot) * 100)); + return { tot, tOff: t, end, dr, done: tot2, tdone, pd, st, buf, deficit, surplus, dailyDelta, net: tdone - pd, pct, ok: tdone >= pd }; +} + +export function dcls(g, i) { + const t = tOff(g); + if (i > t) return 'dot df'; + const v = dTot(g, i); + const c = v === 0 ? 'dot dm' : v >= g.daily * 1.1 ? 'dot db' : v >= g.daily ? 'dot dd' : 'dot dp'; + return c + (editable(g, i) ? ' de' : ' dl'); +} + +export function dlbl(g, i) { + const t = tOff(g); + if (i > t) return String(i + 1); + const v = dTot(g, i); + if (v === 0) return '✕'; + if (v >= g.daily * 1.1) return '+'; + if (v >= g.daily) return '✓'; + return Math.round(v / g.daily * 100) + '%'; +}