Fix "$652.89/hr" on Variable-rate meters (display only)

Waterfront zones (BS/WB/RV/PB/CB) use RateType "Variable" with a placeholder
Rate 652.89 ("Variable Rate - See signs"), not an hourly price. Only render $/hr
for the real "Hour" rate type; show "Variable — see signs" otherwise, and derive
the headline Rate from the current policy instead of the raw zone Rate ("NA"/652.89).
Actual quoting is unaffected — the estimate ladder returns sane per-duration prices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hank 2026-07-07 13:17:46 -07:00
parent d1473f9776
commit a5bfec894e

View file

@ -17,10 +17,28 @@ function fmtMaxTime(m?: number): string {
return `${m} min`; return `${m} min`;
} }
/** Zone rate comes as a string that sometimes already includes a '$' and spaces. */ /** Zone rate is a string that may include '$'/spaces, or be "NA" / a placeholder. */
function fmtRate(r?: string): string | undefined { function fmtRate(r?: string): string | undefined {
if (r == null) return undefined; if (r == null) return undefined;
return '$' + String(r).replace(/^\$\s*/, '').trim(); const n = Number(String(r).replace(/^\$\s*/, '').trim());
return Number.isFinite(n) ? `$${n.toFixed(2)}` : undefined;
}
/**
* A human rate label from the CURRENTLY-active policy. Only "Hour" is a real
* hourly price; "Variable" carries a placeholder number (see-signs), so never
* render it as $/hr.
*/
function currentRateLabel(policy: SpacePolicy | undefined, zoneRate?: string): string {
const t = (policy?.RateType ?? '').toLowerCase();
if (t === 'hour' && typeof policy?.Rate === 'number' && policy.Rate > 0) {
return `$${policy.Rate}/hr`;
}
if (t.includes('variable')) return 'Variable — see signs';
if (t.includes('free')) return 'Free now';
if (t.includes('no parking')) return 'No parking now';
if (t.includes('prepay')) return 'Prepay';
return fmtRate(zoneRate) ?? '—';
} }
/** /**
@ -36,7 +54,10 @@ function policyMeta(p: SpacePolicy): { title: string; rate?: string; color?: str
? '#2e7d32' ? '#2e7d32'
: undefined; : undefined;
const title = p.DisplayString || p.MessageHeader || p.RateType || 'Rate policy'; const title = p.DisplayString || p.MessageHeader || p.RateType || 'Rate policy';
const rate = typeof p.Rate === 'number' && p.Rate > 0 ? `$${p.Rate}/hr` : undefined; // Only a true "Hour" policy is an hourly price; "Variable" etc. carry a
// placeholder number, so don't render those as $/hr.
const rate =
rt === 'hour' && typeof p.Rate === 'number' && p.Rate > 0 ? `$${p.Rate}/hr` : undefined;
return { title, rate, color }; return { title, rate, color };
} }
@ -56,6 +77,7 @@ export function MeterDetailScreen() {
}; };
const firstSpace = z.Spaces?.[0]; const firstSpace = z.Spaces?.[0];
const currentPolicy = firstSpace?.Policies?.find((p) => p.CurrentSlot);
const Field = ({ label, value }: { label: string; value?: string }) => ( const Field = ({ label, value }: { label: string; value?: string }) => (
<View style={[styles.field, { backgroundColor: colors.card }]}> <View style={[styles.field, { backgroundColor: colors.card }]}>
@ -81,7 +103,7 @@ export function MeterDetailScreen() {
<Field label="Serial" value={z.TerminalSerNo} /> <Field label="Serial" value={z.TerminalSerNo} />
</View> </View>
<View style={styles.row}> <View style={styles.row}>
<Field label="Rate" value={fmtRate(z.Rate)} /> <Field label="Rate" value={currentRateLabel(currentPolicy, z.Rate)} />
<Field label="Max time" value={fmtMaxTime(z.MaxTime)} /> <Field label="Max time" value={fmtMaxTime(z.MaxTime)} />
</View> </View>
<View style={styles.row}> <View style={styles.row}>