Use localized units for repeater display

This commit is contained in:
Jack Kingsman
2026-04-12 17:32:07 -07:00
parent b9414e84ee
commit 14f42c59fe
4 changed files with 41 additions and 18 deletions
@@ -1,4 +1,5 @@
import { RepeaterPane, NotFetched, LppSensorRow } from './repeaterPaneShared';
import { useDistanceUnit } from '../../contexts/DistanceUnitContext';
import type { RepeaterLppTelemetryResponse, PaneState } from '../../types';
export function LppTelemetryPane({
@@ -12,6 +13,7 @@ export function LppTelemetryPane({
onRefresh: () => void;
disabled?: boolean;
}) {
const { distanceUnit } = useDistanceUnit();
return (
<RepeaterPane title="LPP Sensors" state={state} onRefresh={onRefresh} disabled={disabled}>
{!data ? (
@@ -21,7 +23,7 @@ export function LppTelemetryPane({
) : (
<div className="space-y-0.5">
{data.sensors.map((sensor, i) => (
<LppSensorRow key={i} sensor={sensor} />
<LppSensorRow key={i} sensor={sensor} unitPref={distanceUnit} />
))}
</div>
)}
@@ -11,7 +11,8 @@ import {
import { cn } from '@/lib/utils';
import { Button } from '../ui/button';
import { Separator } from '../ui/separator';
import { LPP_UNIT_MAP } from './repeaterPaneShared';
import { lppDisplayUnit } from './repeaterPaneShared';
import { useDistanceUnit } from '../../contexts/DistanceUnitContext';
import type { TelemetryHistoryEntry, TelemetryLppSensor, Contact } from '../../types';
const MAX_TRACKED = 8;
@@ -83,6 +84,7 @@ export function TelemetryHistoryPane({
trackedTelemetryRepeaters,
onToggleTrackedTelemetry,
}: TelemetryHistoryPaneProps) {
const { distanceUnit } = useDistanceUnit();
const [metric, setMetric] = useState<string>('battery_volts');
const [toggling, setToggling] = useState(false);
@@ -105,7 +107,7 @@ export function TelemetryHistoryPane({
info.type_name.charAt(0).toUpperCase() +
info.type_name.slice(1).replace(/_/g, ' ') +
` Ch${info.channel}`;
const unit = LPP_UNIT_MAP[info.type_name] ?? '';
const { unit } = lppDisplayUnit(info.type_name, 0, distanceUnit);
result.push({
key: k,
config: { label, unit, color: LPP_COLORS[colorIdx % LPP_COLORS.length] },
@@ -115,7 +117,7 @@ export function TelemetryHistoryPane({
colorIdx++;
}
return result;
}, [entries]);
}, [entries, distanceUnit]);
const allMetricKeys = useMemo(
() => [...BUILTIN_METRICS, ...lppMetrics.map((m) => m.key)],
@@ -145,13 +147,15 @@ export function TelemetryHistoryPane({
packets_sent: d.packets_sent,
uptime_seconds: d.uptime_seconds,
};
// Flatten LPP sensors into the point
// Flatten LPP sensors into the point, converting units as needed
for (const s of d.lpp_sensors ?? []) {
point[lppKey(s)] = typeof s.value === 'number' ? s.value : undefined;
if (typeof s.value === 'number') {
point[lppKey(s)] = lppDisplayUnit(s.type_name, s.value, distanceUnit).value;
}
}
return point;
});
}, [entries]);
}, [entries, distanceUnit]);
const dataKeys =
activeMetric === 'packets' ? ['packets_received', 'packets_sent'] : [activeMetric];
@@ -223,11 +223,26 @@ export const LPP_UNIT_MAP: Record<string, string> = {
colour: '',
};
/**
* Return the display unit and converted value for an LPP sensor,
* respecting the user's unit preference for temperature.
*/
export function lppDisplayUnit(
typeName: string,
value: number,
unitPref: 'metric' | 'imperial' | string
): { unit: string; value: number } {
if (typeName === 'temperature' && unitPref === 'imperial') {
return { unit: '°F', value: (value * 9) / 5 + 32 };
}
return { unit: LPP_UNIT_MAP[typeName] ?? '', value };
}
export function formatLppLabel(typeName: string): string {
return typeName.charAt(0).toUpperCase() + typeName.slice(1).replace(/_/g, ' ');
}
export function LppSensorRow({ sensor }: { sensor: LppSensor }) {
export function LppSensorRow({ sensor, unitPref }: { sensor: LppSensor; unitPref?: string }) {
const label = formatLppLabel(sensor.type_name);
if (typeof sensor.value === 'object' && sensor.value !== null) {
@@ -248,10 +263,10 @@ export function LppSensorRow({ sensor }: { sensor: LppSensor }) {
);
}
const unit = LPP_UNIT_MAP[sensor.type_name] ?? '';
const display = lppDisplayUnit(sensor.type_name, sensor.value as number, unitPref ?? 'metric');
const formatted =
typeof sensor.value === 'number'
? `${sensor.value % 1 === 0 ? sensor.value : sensor.value.toFixed(2)}${unit ? ` ${unit}` : ''}`
? `${display.value % 1 === 0 ? display.value : display.value.toFixed(2)}${display.unit ? ` ${display.unit}` : ''}`
: String(sensor.value);
return <KvRow label={label} value={formatted} />;
@@ -6,7 +6,8 @@ import { Separator } from '../ui/separator';
import { toast } from '../ui/sonner';
import { api } from '../../api';
import { formatTime } from '../../utils/messageParser';
import { LPP_UNIT_MAP } from '../repeater/repeaterPaneShared';
import { lppDisplayUnit } from '../repeater/repeaterPaneShared';
import { useDistanceUnit } from '../../contexts/DistanceUnitContext';
import { BulkDeleteContactsModal } from './BulkDeleteContactsModal';
import type {
AppSettings,
@@ -45,6 +46,7 @@ export function SettingsDatabaseSection({
onToggleTrackedTelemetry?: (publicKey: string) => Promise<void>;
className?: string;
}) {
const { distanceUnit } = useDistanceUnit();
const [retentionDays, setRetentionDays] = useState('14');
const [cleaning, setCleaning] = useState(false);
const [purgingDecryptedRaw, setPurgingDecryptedRaw] = useState(false);
@@ -310,18 +312,18 @@ export function SettingsDatabaseSection({
tx {d.packets_sent != null ? d.packets_sent.toLocaleString() : '?'}
</span>
{d.lpp_sensors?.map((s) => {
const unit = LPP_UNIT_MAP[s.type_name] ?? '';
const display = lppDisplayUnit(s.type_name, s.value, distanceUnit);
const val =
typeof s.value === 'number'
? s.value % 1 === 0
? s.value
: s.value.toFixed(1)
: s.value;
typeof display.value === 'number'
? display.value % 1 === 0
? display.value
: display.value.toFixed(1)
: display.value;
const label = s.type_name.charAt(0).toUpperCase() + s.type_name.slice(1);
return (
<span key={`${s.type_name}-${s.channel}`}>
{label} {val}
{unit ? ` ${unit}` : ''}
{display.unit ? ` ${display.unit}` : ''}
</span>
);
})}