diff --git a/src/components/admin/editors/CoasterStatsEditor.tsx b/src/components/admin/editors/CoasterStatsEditor.tsx index d1c4ccae..97c99765 100644 --- a/src/components/admin/editors/CoasterStatsEditor.tsx +++ b/src/components/admin/editors/CoasterStatsEditor.tsx @@ -5,6 +5,13 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Card } from "@/components/ui/card"; +import { useUnitPreferences } from "@/hooks/useUnitPreferences"; +import { + convertValueToMetric, + convertValueFromMetric, + detectUnitType, + getMetricUnit +} from "@/lib/units"; interface CoasterStat { stat_name: string; @@ -38,6 +45,7 @@ export function CoasterStatsEditor({ onChange, categories = DEFAULT_CATEGORIES }: CoasterStatsEditorProps) { + const { preferences } = useUnitPreferences(); const addStat = () => { onChange([ @@ -78,6 +86,23 @@ export function CoasterStatsEditor({ onChange(newStats); }; + // Get display value (convert from metric if needed) + const getDisplayValue = (stat: CoasterStat): string => { + if (!stat.stat_value || !stat.unit) return String(stat.stat_value || ''); + + const numValue = Number(stat.stat_value); + if (isNaN(numValue)) return String(stat.stat_value); + + const unitType = detectUnitType(stat.unit); + if (unitType === 'unknown') return String(stat.stat_value); + + // Assume stored value is in metric, convert to user's preferred units + const metricUnit = getMetricUnit(stat.unit); + const displayValue = convertValueFromMetric(numValue, stat.unit, metricUnit); + + return String(displayValue); + }; + return (
@@ -129,10 +154,27 @@ export function CoasterStatsEditor({ updateStat(index, 'stat_value', parseFloat(e.target.value) || 0)} + value={getDisplayValue(stat)} + onChange={(e) => { + const inputValue = e.target.value; + // If unit is recognized, convert to metric for storage + if (stat.unit) { + const numValue = parseFloat(inputValue); + if (!isNaN(numValue)) { + const metricValue = convertValueToMetric(numValue, stat.unit); + updateStat(index, 'stat_value', metricValue); + } else { + updateStat(index, 'stat_value', 0); + } + } else { + updateStat(index, 'stat_value', parseFloat(inputValue) || 0); + } + }} placeholder="0" /> +

+ Using {preferences.measurement_system} units +

diff --git a/src/components/admin/editors/TechnicalSpecsEditor.tsx b/src/components/admin/editors/TechnicalSpecsEditor.tsx index c9569b82..c754fcd8 100644 --- a/src/components/admin/editors/TechnicalSpecsEditor.tsx +++ b/src/components/admin/editors/TechnicalSpecsEditor.tsx @@ -4,6 +4,13 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Card } from "@/components/ui/card"; +import { useUnitPreferences } from "@/hooks/useUnitPreferences"; +import { + convertValueToMetric, + convertValueFromMetric, + detectUnitType, + getMetricUnit +} from "@/lib/units"; interface TechnicalSpec { spec_name: string; @@ -30,6 +37,7 @@ export function TechnicalSpecsEditor({ categories = DEFAULT_CATEGORIES, commonSpecs = [] }: TechnicalSpecsEditorProps) { + const { preferences } = useUnitPreferences(); const addSpec = () => { onChange([ @@ -57,6 +65,23 @@ export function TechnicalSpecsEditor({ onChange(newSpecs); }; + // Get display value (convert from metric if needed) + const getDisplayValue = (spec: TechnicalSpec): string => { + if (!spec.spec_value || !spec.unit || spec.spec_type !== 'number') return spec.spec_value; + + const numValue = parseFloat(spec.spec_value); + if (isNaN(numValue)) return spec.spec_value; + + const unitType = detectUnitType(spec.unit); + if (unitType === 'unknown') return spec.spec_value; + + // Assume stored value is in metric, convert to user's preferred units + const metricUnit = getMetricUnit(spec.unit); + const displayValue = convertValueFromMetric(numValue, spec.unit, metricUnit); + + return String(displayValue); + }; + const moveSpec = (index: number, direction: 'up' | 'down') => { if ((direction === 'up' && index === 0) || (direction === 'down' && index === specs.length - 1)) { return; @@ -107,11 +132,30 @@ export function TechnicalSpecsEditor({
updateSpec(index, 'spec_value', e.target.value)} + value={getDisplayValue(spec)} + onChange={(e) => { + const inputValue = e.target.value; + // If type is number and unit is recognized, convert to metric for storage + if (spec.spec_type === 'number' && spec.unit) { + const numValue = parseFloat(inputValue); + if (!isNaN(numValue)) { + const metricValue = convertValueToMetric(numValue, spec.unit); + updateSpec(index, 'spec_value', String(metricValue)); + } else { + updateSpec(index, 'spec_value', inputValue); + } + } else { + updateSpec(index, 'spec_value', inputValue); + } + }} placeholder="Value" type={spec.spec_type === 'number' ? 'number' : 'text'} /> + {spec.spec_type === 'number' && spec.unit && ( +

+ Using {preferences.measurement_system} units +

+ )}
diff --git a/src/lib/units.ts b/src/lib/units.ts index 58dd38de..dfa52122 100644 --- a/src/lib/units.ts +++ b/src/lib/units.ts @@ -75,4 +75,133 @@ export const IMPERIAL_COUNTRIES = ['US', 'LR', 'MM']; // Detect measurement system from country code export function getMeasurementSystemFromCountry(countryCode: string): MeasurementSystem { return IMPERIAL_COUNTRIES.includes(countryCode.toUpperCase()) ? 'imperial' : 'metric'; +} + +// Unit type detection +export type UnitType = 'speed' | 'distance' | 'height' | 'weight' | 'unknown'; + +export function detectUnitType(unit: string): UnitType { + const normalized = unit.toLowerCase().trim(); + + // Speed units + if (['km/h', 'kmh', 'kph', 'mph', 'm/s', 'ms'].includes(normalized)) { + return 'speed'; + } + + // Distance units (meters/feet) + if (['m', 'meter', 'meters', 'metre', 'metres', 'ft', 'feet', 'foot'].includes(normalized)) { + return 'distance'; + } + + // Height units (cm/inches) + if (['cm', 'centimeter', 'centimeters', 'in', 'inch', 'inches'].includes(normalized)) { + return 'height'; + } + + // Weight units + if (['kg', 'kilogram', 'kilograms', 'lb', 'lbs', 'pound', 'pounds'].includes(normalized)) { + return 'weight'; + } + + return 'unknown'; +} + +// Convert any value to metric based on its unit +export function convertValueToMetric(value: number, unit: string): number { + const normalized = unit.toLowerCase().trim(); + + // Speed conversions to km/h + if (normalized === 'mph') { + return Math.round(value / 0.621371); + } + if (['m/s', 'ms'].includes(normalized)) { + return Math.round(value * 3.6); + } + if (['km/h', 'kmh', 'kph'].includes(normalized)) { + return Math.round(value); + } + + // Distance conversions to meters + if (['ft', 'feet', 'foot'].includes(normalized)) { + return Math.round(value / 3.28084); + } + if (['m', 'meter', 'meters', 'metre', 'metres'].includes(normalized)) { + return Math.round(value); + } + + // Height conversions to cm + if (['in', 'inch', 'inches'].includes(normalized)) { + return Math.round(value / 0.393701); + } + if (['cm', 'centimeter', 'centimeters'].includes(normalized)) { + return Math.round(value); + } + + // Weight conversions to kg + if (['lb', 'lbs', 'pound', 'pounds'].includes(normalized)) { + return Math.round(value / 2.20462); + } + if (['kg', 'kilogram', 'kilograms'].includes(normalized)) { + return Math.round(value); + } + + // Unknown unit, return as-is + return value; +} + +// Convert metric value to target unit +export function convertValueFromMetric(value: number, targetUnit: string, metricUnit: string): number { + const normalized = targetUnit.toLowerCase().trim(); + const metricNormalized = metricUnit.toLowerCase().trim(); + + // Speed conversions from km/h + if (metricNormalized === 'km/h' || metricNormalized === 'kmh' || metricNormalized === 'kph') { + if (normalized === 'mph') { + return Math.round(value * 0.621371); + } + if (normalized === 'm/s' || normalized === 'ms') { + return Math.round(value / 3.6); + } + } + + // Distance conversions from meters + if (metricNormalized === 'm' || metricNormalized === 'meter' || metricNormalized === 'meters') { + if (['ft', 'feet', 'foot'].includes(normalized)) { + return Math.round(value * 3.28084); + } + } + + // Height conversions from cm + if (metricNormalized === 'cm' || metricNormalized === 'centimeter' || metricNormalized === 'centimeters') { + if (['in', 'inch', 'inches'].includes(normalized)) { + return Math.round(value * 0.393701); + } + } + + // Weight conversions from kg + if (metricNormalized === 'kg' || metricNormalized === 'kilogram' || metricNormalized === 'kilograms') { + if (['lb', 'lbs', 'pound', 'pounds'].includes(normalized)) { + return Math.round(value * 2.20462); + } + } + + return value; +} + +// Get metric unit for a given unit type +export function getMetricUnit(unit: string): string { + const unitType = detectUnitType(unit); + + switch (unitType) { + case 'speed': + return 'km/h'; + case 'distance': + return 'm'; + case 'height': + return 'cm'; + case 'weight': + return 'kg'; + default: + return unit; + } } \ No newline at end of file