diff --git a/src/actions/water-log/field.ts b/src/actions/water-log/field.ts index b6af910..f2c6961 100644 --- a/src/actions/water-log/field.ts +++ b/src/actions/water-log/field.ts @@ -70,6 +70,13 @@ type FieldHeadgate = { notes: string | null; /** ISO timestamp of the most recent successful log. Display only. */ last_used_at: string | null; + /** + * Unit from the most recent log entry for this headgate, or + * `null` if the headgate has never been logged against. Drives the + * log-form unit picker — operators don't have to re-pick their + * preferred unit on the same gate every shift. + */ + last_unit: string | null; }; type VerifyPinResult = @@ -100,8 +107,33 @@ export async function getWaterHeadgates( eq(waterHeadgates.active, true), ) : eq(waterHeadgates.brandId, TUXEDO_BRAND_ID); + // Most-recent unit logged against this headgate. `null` when no + // entries exist yet — fall back to `waterHeadgates.unit` on the + // client. Per-headgate PK + the (logged_at DESC) index makes + // this O(log n) per gate; for typical Tuxedo fleet sizes + // (<200 active gates × handful of entries each), the planner + // will hash-join on the headgate_id PK and never touch disk. + const lastUnitSql = sql`( + SELECT ${waterLogEntries.unit} + FROM ${waterLogEntries} + WHERE ${waterLogEntries.headgateId} = ${waterHeadgates.id} + AND ${waterLogEntries.brandId} = ${TUXEDO_BRAND_ID} + ORDER BY ${waterLogEntries.loggedAt} DESC + LIMIT 1 + )`; const rows = await db - .select() + .select({ + id: waterHeadgates.id, + name: waterHeadgates.name, + unit: waterHeadgates.unit, + status: waterHeadgates.status, + highThreshold: waterHeadgates.highThreshold, + lowThreshold: waterHeadgates.lowThreshold, + active: waterHeadgates.active, + notes: waterHeadgates.notes, + lastUsedAt: waterHeadgates.lastUsedAt, + lastUnit: lastUnitSql, + }) .from(waterHeadgates) .where(where) .orderBy(waterHeadgates.name); @@ -115,6 +147,7 @@ export async function getWaterHeadgates( active: h.active, notes: h.notes, last_used_at: h.lastUsedAt ? h.lastUsedAt.toISOString() : null, + last_unit: h.lastUnit, })); }); } diff --git a/src/components/water/mobile/LogForm.tsx b/src/components/water/mobile/LogForm.tsx index da3e573..6474244 100644 --- a/src/components/water/mobile/LogForm.tsx +++ b/src/components/water/mobile/LogForm.tsx @@ -66,9 +66,15 @@ export function MobileWaterLogForm({ const { t, lang } = useLang(); // ── State ──────────────────────────────────────────────────────── const [measurement, setMeasurement] = useState(""); - const [unit, setUnit] = useState( - units.includes(headgate.unit) ? headgate.unit : units[0], - ); + // Unit priority: most-recent log > headgate's static config > first + // option. `last_unit` is the operator's *last* choice on this gate + // across the whole fleet — far better than re-picking every shift. + const [unit, setUnit] = useState(() => { + const remembered = headgate.last_unit; + if (remembered && units.includes(remembered)) return remembered; + if (units.includes(headgate.unit)) return headgate.unit; + return units[0]; + }); const [notes, setNotes] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); diff --git a/src/components/water/mobile/types.ts b/src/components/water/mobile/types.ts index 981e5fb..b105341 100644 --- a/src/components/water/mobile/types.ts +++ b/src/components/water/mobile/types.ts @@ -18,6 +18,13 @@ export type FieldHeadgate = { notes: string | null; /** ISO timestamp of last successful log. */ last_used_at: string | null; + /** + * Unit from the most recent log entry for this headgate, or + * `null` if none have been logged yet. The log form's unit picker + * prefers this over the headgate's configured `unit` so operators + * don't have to re-pick on every visit. + */ + last_unit: string | null; }; /** The units the mobile app offers in its segmented control.