Allow viewing past days + enforce edit cutoff server-side
- Clicking any past day dot now opens a stats panel (read-only for days older than yesterday) - Entry form and delete buttons hidden for non-editable days - Backend silently restores locked offsets (< yesterday) on PATCH, preventing backdated edits - Negative buffer no longer shows green: badge and progress bar are amber/red when buf < 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
526d851eef
commit
da6eed8803
2 changed files with 32 additions and 17 deletions
|
|
@ -37,7 +37,7 @@ function delGoal(id) {
|
|||
|
||||
function selD(gid, off) {
|
||||
const g = state.goals.find(x => x.id === gid);
|
||||
if (!g || !editable(g, off)) return;
|
||||
if (!g) return;
|
||||
state.selDay[gid] = state.selDay[gid] === off ? null : off;
|
||||
render();
|
||||
}
|
||||
|
|
@ -70,10 +70,11 @@ function buildNameWrap(g) {
|
|||
}
|
||||
|
||||
function buildPanel(g, off) {
|
||||
const t = tOff(g), sets = g.sets[String(off)] || [], tot = dTot(g, off);
|
||||
const lbl = off === t ? tr('heute') : tr('gestern'), k = g.id + '_' + off;
|
||||
const t = tOff(g), sets = g.sets[String(off)] || [], tot = dTot(g, off), ed = editable(g, off);
|
||||
const lbl = off === t ? tr('heute') : off === t - 1 ? tr('gestern') : null;
|
||||
const k = g.id + '_' + off;
|
||||
const el = tpl('tpl-panel');
|
||||
el.querySelector('.dpanel-title').textContent = lbl + ' — ' + fd(o2d(g, off));
|
||||
el.querySelector('.dpanel-title').textContent = (lbl ? lbl + ' — ' : '') + fd(o2d(g, off));
|
||||
el.querySelector('.dpanel-sub').textContent = tot + ' / ' + g.daily + ' ' + g.unit;
|
||||
const body = el.querySelector('.dpanel-body');
|
||||
if (sets.length) {
|
||||
|
|
@ -86,29 +87,31 @@ function buildPanel(g, off) {
|
|||
const strong = document.createElement('strong'); strong.textContent = s.amount;
|
||||
span.appendChild(strong); span.appendChild(document.createTextNode(' ' + g.unit));
|
||||
const btn = row.querySelector('.sdel');
|
||||
btn.dataset.g = g.id; btn.dataset.o = off; btn.dataset.i = i;
|
||||
if (ed) { btn.dataset.g = g.id; btn.dataset.o = off; btn.dataset.i = i; } else { btn.remove(); }
|
||||
body.appendChild(row);
|
||||
}
|
||||
} else {
|
||||
body.appendChild(tpl('tpl-nosets'));
|
||||
}
|
||||
const addRow = tpl('tpl-add-row');
|
||||
const inp = addRow.querySelector('.num-in');
|
||||
inp.placeholder = g.daily; inp.value = state.addAmt[k] || ''; inp.dataset.k = k; inp.dataset.g = g.id; inp.dataset.o = off;
|
||||
const abtn = addRow.querySelector('.btn-as');
|
||||
abtn.dataset.g = g.id; abtn.dataset.o = off;
|
||||
addRow.querySelector('.ulbl').textContent = g.unit;
|
||||
body.appendChild(addRow);
|
||||
if (ed) {
|
||||
const addRow = tpl('tpl-add-row');
|
||||
const inp = addRow.querySelector('.num-in');
|
||||
inp.placeholder = g.daily; inp.value = state.addAmt[k] || ''; inp.dataset.k = k; inp.dataset.g = g.id; inp.dataset.o = off;
|
||||
const abtn = addRow.querySelector('.btn-as');
|
||||
abtn.dataset.g = g.id; abtn.dataset.o = off;
|
||||
addRow.querySelector('.ulbl').textContent = g.unit;
|
||||
body.appendChild(addRow);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function buildCard(g) {
|
||||
const c = calc(g), t = c.tOff;
|
||||
const fc = c.surplus > 0 ? 'var(--blue)' : c.st === 0 ? 'var(--green)' : c.dailyDelta <= 0 ? 'var(--green)' : c.dailyDelta <= g.daily * .2 ? 'var(--amber)' : 'var(--red)';
|
||||
const fc = c.surplus > 0 ? 'var(--blue)' : c.st === 0 && c.buf >= 0 ? 'var(--green)' : c.dailyDelta <= 0 ? 'var(--green)' : c.dailyDelta <= g.daily * .2 ? 'var(--amber)' : 'var(--red)';
|
||||
let bc, bt;
|
||||
const bufStr = (c.buf > 0 ? '+' : '') + c.buf;
|
||||
if (c.ok && c.surplus > 0) { bc = 'b-buf'; bt = bufStr; }
|
||||
else if (c.ok) { bc = 'b-done'; bt = bufStr; }
|
||||
else if (c.ok && c.buf >= 0) { bc = 'b-done'; bt = bufStr; }
|
||||
else if (c.dailyDelta <= 0) { bc = 'b-ok'; bt = bufStr; }
|
||||
else if (c.dailyDelta <= g.daily * .2) { bc = 'b-warn'; bt = bufStr; }
|
||||
else { bc = 'b-danger'; bt = bufStr; }
|
||||
|
|
@ -153,7 +156,7 @@ function buildCard(g) {
|
|||
const it = i === t, iy = i === t - 1, is = sel === i, ed = editable(g, i);
|
||||
const dot = tpl('tpl-dot');
|
||||
dot.className = dcls(g, i) + (is ? ' rs' : it ? ' rt' : iy && t > 0 ? ' ry' : '');
|
||||
if (ed) { dot.dataset.g = g.id; dot.dataset.d = i; }
|
||||
if (i <= t) { dot.dataset.g = g.id; dot.dataset.d = i; }
|
||||
dot.textContent = dlbl(g, i);
|
||||
dotsWrap.appendChild(dot);
|
||||
}
|
||||
|
|
@ -266,7 +269,7 @@ function wire() {
|
|||
inp.onkeydown = function(e) { if (e.key === 'Enter') commitRen(gid); if (e.key === 'Escape') cancelRen(); };
|
||||
inp.onblur = function() { commitRen(gid); };
|
||||
});
|
||||
document.querySelectorAll('.de').forEach(d => {
|
||||
document.querySelectorAll('.de, .dl').forEach(d => {
|
||||
d.onclick = function(e) { e.stopPropagation(); selD(this.dataset.g, parseInt(this.dataset.d, 10)); };
|
||||
});
|
||||
document.querySelectorAll('.btn-as').forEach(b => {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,19 @@ class GoalController extends AbstractController
|
|||
if (isset($data['unit'])) $goal->setUnit((string)$data['unit']);
|
||||
if (isset($data['daily'])) $goal->setDaily((float)$data['daily']);
|
||||
if (isset($data['days'])) $goal->setDays((int)$data['days']);
|
||||
if (isset($data['sets'])) $goal->setSets((array)$data['sets']);
|
||||
if (isset($data['sets'])) {
|
||||
$newSets = (array)$data['sets'];
|
||||
$existing = $goal->getSets();
|
||||
$startTs = (clone $goal->getStart())->setTime(0, 0, 0)->getTimestamp();
|
||||
$todayOffset = (int)round((mktime(0, 0, 0) - $startTs) / 86400);
|
||||
foreach ($existing as $offset => $entries) {
|
||||
if ((int)$offset < $todayOffset - 1) $newSets[(string)$offset] = $entries;
|
||||
}
|
||||
foreach (array_keys($newSets) as $offset) {
|
||||
if ((int)$offset < $todayOffset - 1 && !array_key_exists((string)$offset, $existing)) unset($newSets[$offset]);
|
||||
}
|
||||
$goal->setSets($newSets);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
return new JsonResponse(['ok' => true]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue