mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 15:59:02 +00:00
fix(goals): address second AI review round on PR #1798
- Parse "YYYY-MM-DD" date-only strings as local midnight in the projection chart so users west of UTC stop seeing the today marker and hover dates land one calendar day back - Order the demo-generator depository pickup by (created_at, id) so primary/secondary roles stay stable across reseeds and the state matrix (behind / on_track / reached / no_target_date / past-due) surfaces the same goals every time - Drop the brittle " · "-split on goals.goal_card.days_left in Goal#header_summary (the translation has no separator suffix) - Goal#projection_payload ships pre-formatted strings for the static chart annotations (target_amount_label / short, projection_end_label, projection_shortfall_label, pending_pledge_label_short) and the controller now renders those instead of running Intl.NumberFormat on each draw. Y-axis tick labels stay JS-side because they depend on D3's dynamically-chosen tick values.
This commit is contained in:
@@ -81,9 +81,18 @@ export default class extends Controller {
|
||||
const innerWidth = width - margin.left - margin.right;
|
||||
const innerHeight = height - margin.top - margin.bottom;
|
||||
|
||||
const start = new Date(data.start_date);
|
||||
const today = new Date(data.today);
|
||||
const target = data.target_date ? new Date(data.target_date) : null;
|
||||
// Date-only payload strings ("YYYY-MM-DD") parse as UTC midnight in
|
||||
// `new Date(str)`, which shifts displayed days back one for users west
|
||||
// of Greenwich. Parse components so today/target/saved_series sit on
|
||||
// local-midnight.
|
||||
const parseLocalDate = (s) => {
|
||||
if (!s) return null;
|
||||
const [ y, m, d ] = s.split("-").map(Number);
|
||||
return new Date(y, m - 1, d);
|
||||
};
|
||||
const start = parseLocalDate(data.start_date);
|
||||
const today = parseLocalDate(data.today);
|
||||
const target = parseLocalDate(data.target_date);
|
||||
const targetAmount = data.target_amount || 0;
|
||||
const currentAmount = data.current_amount || 0;
|
||||
const avgMonthly = data.avg_monthly || 0;
|
||||
@@ -100,7 +109,7 @@ export default class extends Controller {
|
||||
// Without this, the snapshot in `balances` for today could differ from
|
||||
// the live read (sync timing) and the chart showed a vertical jump.
|
||||
const rawSavedSeries = (data.saved_series || [])
|
||||
.map((p) => ({ date: new Date(p.date), value: p.value }))
|
||||
.map((p) => ({ date: parseLocalDate(p.date), value: p.value }))
|
||||
.filter((p) => p.date < today);
|
||||
const firstContribDate = rawSavedSeries[0]?.date;
|
||||
const savedSeries = [];
|
||||
@@ -219,7 +228,7 @@ export default class extends Controller {
|
||||
.attr("text-anchor", "end")
|
||||
.attr("font-size", 12)
|
||||
.attr("fill", textPrimary)
|
||||
.text(`Target · ${this._fmtMoneyShort(targetAmount, data.currency)}`);
|
||||
.text(`Target · ${data.target_amount_short_label}`);
|
||||
} else {
|
||||
// Plenty of room: keep the right-side full-format label.
|
||||
svg
|
||||
@@ -229,7 +238,7 @@ export default class extends Controller {
|
||||
.attr("text-anchor", "end")
|
||||
.attr("font-size", 12)
|
||||
.attr("fill", textPrimary)
|
||||
.text(`Target · ${this._fmtMoney(targetAmount, data.currency)}`);
|
||||
.text(`Target · ${data.target_amount_label}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,20 +318,22 @@ export default class extends Controller {
|
||||
const collidesWithTargetLabel = targetAmount > 0 && Math.abs(projDotY - y(targetAmount)) < 18;
|
||||
|
||||
if (innerWidth >= 320 && !(willHit && collidesWithTargetLabel)) {
|
||||
// Full Intl.NumberFormat (no K/M shorthand) so the chart annotation
|
||||
// matches the rest of the page's monetary readouts ("$160,634
|
||||
// short" reads cleanly next to "$26,621/mo to catch up").
|
||||
// Server-rendered labels: projection_end_label is the full-format
|
||||
// currency for the on-track endpoint, projection_shortfall_label
|
||||
// is the "$X short" string when we fall short.
|
||||
const labelText = willHit
|
||||
? this._fmtMoney(projectionEnd, data.currency)
|
||||
: `${this._fmtMoney(targetAmount - projectionEnd, data.currency)} short`;
|
||||
svg
|
||||
.append("text")
|
||||
.attr("x", x(target) - 8)
|
||||
.attr("y", y(projectionEnd) - 8)
|
||||
.attr("text-anchor", "end")
|
||||
.attr("font-size", 12)
|
||||
.attr("fill", textSecondary)
|
||||
.text(labelText);
|
||||
? data.projection_end_label
|
||||
: (data.projection_shortfall_label ? `${data.projection_shortfall_label} short` : "");
|
||||
if (labelText) {
|
||||
svg
|
||||
.append("text")
|
||||
.attr("x", x(target) - 8)
|
||||
.attr("y", y(projectionEnd) - 8)
|
||||
.attr("text-anchor", "end")
|
||||
.attr("font-size", 12)
|
||||
.attr("fill", textSecondary)
|
||||
.text(labelText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +369,7 @@ export default class extends Controller {
|
||||
.attr("y", y(pendingTop) + 4)
|
||||
.attr("font-size", 12)
|
||||
.attr("fill", textSecondary)
|
||||
.text(`+ pending ${this._fmtMoneyShort(pendingPledgeAmount, data.currency)}`);
|
||||
.text(`+ pending ${data.pending_pledge_label_short}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user