import { Plus, Trash2 } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; 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 { toast } from "sonner"; import { convertValueToMetric, convertValueFromMetric, detectUnitType, getMetricUnit, getDisplayUnit } from "@/lib/units"; import { validateMetricUnit, METRIC_UNITS } from "@/lib/unitValidation"; import { getErrorMessage } from "@/lib/errorHandler"; interface TechnicalSpec { spec_name: string; spec_value: string; spec_type: 'string' | 'number' | 'boolean' | 'date'; category?: string; unit?: string; display_order: number; } interface TechnicalSpecsEditorProps { specs: TechnicalSpec[]; onChange: (specs: TechnicalSpec[]) => void; categories?: string[]; commonSpecs?: string[]; } const DEFAULT_CATEGORIES = ['Performance', 'Safety', 'Design', 'Capacity', 'Technical', 'Other']; export function TechnicalSpecsEditor({ specs, onChange, categories = DEFAULT_CATEGORIES, commonSpecs = [] }: TechnicalSpecsEditorProps) { const { preferences } = useUnitPreferences(); const [unitErrors, setUnitErrors] = useState>({}); const addSpec = () => { onChange([ ...specs, { spec_name: '', spec_value: '', spec_type: 'string', category: categories[0], unit: '', display_order: specs.length } ]); }; const removeSpec = (index: number) => { const newSpecs = specs.filter((_, i) => i !== index); // Reorder display_order onChange(newSpecs.map((spec, i) => ({ ...spec, display_order: i }))); }; const updateSpec = (index: number, field: keyof TechnicalSpec, value: string | number | boolean | null | undefined) => { const newSpecs = [...specs]; // Ensure unit is metric when updating unit field if (field === 'unit' && value && typeof value === 'string') { try { validateMetricUnit(value, 'Unit'); newSpecs[index] = { ...newSpecs[index], unit: value }; // Clear error for this index setUnitErrors(prev => { const updated = { ...prev }; delete updated[index]; return updated; }); } catch (error: unknown) { const message = getErrorMessage(error); toast.error(message); // Store error for visual feedback setUnitErrors(prev => ({ ...prev, [index]: message })); return; } } else { newSpecs[index] = { ...newSpecs[index], [field]: value }; } onChange(newSpecs); }; // Get display value (convert from metric to user's preferred units) 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; // spec.unit is the metric unit (e.g., "km/h") // Get the display unit based on user preference (e.g., "mph" for imperial) const displayUnit = getDisplayUnit(spec.unit, preferences.measurement_system); // Convert from metric to display unit const displayValue = convertValueFromMetric(numValue, displayUnit, spec.unit); return String(displayValue); }; const moveSpec = (index: number, direction: 'up' | 'down') => { if ((direction === 'up' && index === 0) || (direction === 'down' && index === specs.length - 1)) { return; } const newSpecs = [...specs]; const swapIndex = direction === 'up' ? index - 1 : index + 1; [newSpecs[index], newSpecs[swapIndex]] = [newSpecs[swapIndex], newSpecs[index]]; // Update display_order newSpecs[index].display_order = index; newSpecs[swapIndex].display_order = swapIndex; onChange(newSpecs); }; return (
{specs.length === 0 ? ( No specifications added yet. Click "Add Specification" to get started. ) : (
{specs.map((spec, index) => (
updateSpec(index, 'spec_name', e.target.value)} placeholder="e.g., Track Material" list={`common-specs-${index}`} /> {commonSpecs.length > 0 && ( {commonSpecs.map(s => )}
{ const inputValue = e.target.value; const numValue = parseFloat(inputValue); // If type is number and unit is recognized, convert to metric for storage if (spec.spec_type === 'number' && spec.unit && !isNaN(numValue)) { // Determine what unit the user is entering (based on their preference) const displayUnit = getDisplayUnit(spec.unit, preferences.measurement_system); // Convert from user's input unit to metric for storage const metricValue = convertValueToMetric(numValue, displayUnit); updateSpec(index, 'spec_value', String(metricValue)); } else { updateSpec(index, 'spec_value', inputValue); } }} placeholder="Value" type={spec.spec_type === 'number' ? 'number' : 'text'} /> {spec.spec_type === 'number' && spec.unit && detectUnitType(spec.unit) !== 'unknown' && (

Enter in {getDisplayUnit(spec.unit, preferences.measurement_system)}

)}
updateSpec(index, 'unit', e.target.value)} placeholder="Unit" list={`units-${index}`} className={unitErrors[index] ? 'border-destructive' : ''} /> {METRIC_UNITS.map(u => {unitErrors[index] && (

{unitErrors[index]}

)}

⚠️ Metric units only

))}
)}
); } /** * Validates technical specs before submission */ export function validateTechnicalSpecs(specs: TechnicalSpec[]): { valid: boolean; errors: string[] } { const errors: string[] = []; specs.forEach((spec, index) => { if (!spec.spec_name?.trim()) { errors.push(`Spec ${index + 1}: Name is required`); } if (!spec.spec_value?.trim()) { errors.push(`Spec ${index + 1} (${spec.spec_name}): Value is required`); } if (spec.unit) { try { validateMetricUnit(spec.unit, `Spec ${index + 1} (${spec.spec_name})`); } catch (error: unknown) { errors.push(getErrorMessage(error)); } } }); return { valid: errors.length === 0, errors }; }