fix(tasks): keep a task whole through import and edit, and fix two counts
Review found that import and edit rebuilt a task from this build's parse. That stripped any field it didn't know, and import dropped a newer version's task entirely. Both now keep the record as stored and validate only to display it. An event whose goal is points can't be ticked, so it no longer counts as waiting today: it counts with its week. The month-day field reset itself to 1 when emptied; it now keeps a draft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54d8424d1e
commit
5a4c746a44
7 files changed
+110
-28
No files matched your search
@@ -61,9 +61,11 @@ export interface DailyItem {
|
||||
goal?: GoalStatus | undefined;
|
||||
/**
|
||||
* How often it is due. Chores and events are daily and make up the header's
|
||||
* "today" count; tasks are counted by their own period beside it.
|
||||
* "today" count; tasks are counted by their own period beside it. An event
|
||||
* whose goal is a number has no "today" — nothing ticks — so it counts with
|
||||
* its week, or, over its whole run, in no tally at all.
|
||||
*/
|
||||
cadence: "day" | "week" | "month";
|
||||
cadence: "day" | "week" | "month" | "event";
|
||||
/** A task's period, for finding the one before it in Catch up. */
|
||||
period?: Period | undefined;
|
||||
/** A task's unit, printed after its target. */
|
||||
@@ -149,6 +151,7 @@ export function dailyGroups(
|
||||
|
||||
for (const event of events) {
|
||||
if (event.game !== lane) continue;
|
||||
const goal = goalFor(event.id);
|
||||
items.push({
|
||||
key: event.id,
|
||||
game: lane,
|
||||
@@ -158,8 +161,8 @@ export function dailyGroups(
|
||||
today,
|
||||
resetsIn,
|
||||
notBefore: startOf(event),
|
||||
goal: goalFor(event.id),
|
||||
cadence: "day",
|
||||
goal,
|
||||
cadence: goal?.measure !== "amount" ? "day" : goal.scope === "week" ? "week" : "event",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -221,6 +224,7 @@ export function dailyTally(
|
||||
const month = { total: 0, complete: 0 };
|
||||
for (const i of items) {
|
||||
const met = i.goal?.met === true;
|
||||
if (i.cadence === "event") continue;
|
||||
if (i.cadence !== "day") {
|
||||
const tally = i.cadence === "week" ? week : month;
|
||||
tally.total += 1;
|
||||
|
||||
@@ -13,23 +13,32 @@ export interface TaskFields {
|
||||
title: string;
|
||||
unit: "week" | "month";
|
||||
weekday: number;
|
||||
monthDay: number;
|
||||
/** As typed, so the field can be emptied and retyped; read through `parseMonthDay`. */
|
||||
monthDay: string | number;
|
||||
kind: "days" | "amount";
|
||||
required: string;
|
||||
target: string;
|
||||
amountUnit: string;
|
||||
}
|
||||
|
||||
/** A typed day of the month, clamped to 1–31; null while empty or unreadable. */
|
||||
export function parseMonthDay(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? Math.min(31, Math.max(1, n)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The task these fields describe, or null while they do not describe one the
|
||||
* store would accept. Checked against `TaskSchema` itself, so the form can
|
||||
* never offer Save on something the store would then refuse.
|
||||
*/
|
||||
export function taskDraftFrom(f: TaskFields): TaskDraft | null {
|
||||
const monthDay = parseMonthDay(String(f.monthDay));
|
||||
if (f.unit === "month" && monthDay === null) return null;
|
||||
const period =
|
||||
f.unit === "week"
|
||||
? ({ unit: "week", startsOn: f.weekday } as const)
|
||||
: ({ unit: "month", startsOn: f.monthDay } as const);
|
||||
: ({ unit: "month", startsOn: monthDay ?? 1 } as const);
|
||||
const unit = f.amountUnit.trim();
|
||||
const target = parseAmount(f.target);
|
||||
const measure =
|
||||
@@ -78,7 +87,7 @@ export function TaskForm({
|
||||
title: initial?.title ?? "",
|
||||
unit: initial?.period.unit ?? "week",
|
||||
weekday: initial?.period.unit === "week" ? initial.period.startsOn : 1,
|
||||
monthDay: initial?.period.unit === "month" ? initial.period.startsOn : 1,
|
||||
monthDay: String(initial?.period.unit === "month" ? initial.period.startsOn : 1),
|
||||
kind: initial?.measure.kind ?? "days",
|
||||
required: initial?.measure.kind === "days" ? String(initial.measure.required) : "1",
|
||||
target: initial?.measure.kind === "amount" ? String(initial.measure.target) : "",
|
||||
@@ -156,7 +165,7 @@ export function TaskForm({
|
||||
min={1}
|
||||
max={31}
|
||||
value={f.monthDay}
|
||||
onChange={(e) => set({ monthDay: Number.parseInt(e.target.value, 10) || 1 })}
|
||||
onChange={(e) => set({ monthDay: e.target.value })}
|
||||
className={`tnum w-14 ${field}`}
|
||||
/>
|
||||
</label>
|
||||
|
||||
Reference in new issue
Block a user