Refactor code structure and remove redundant changes

This commit is contained in:
pacnpal
2025-11-09 16:31:34 -05:00
parent 2884bc23ce
commit eb68cf40c6
1080 changed files with 27361 additions and 56687 deletions

View File

@@ -0,0 +1,287 @@
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<Record<number, string>>({});
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 (
<div className="space-y-4">
<div className="flex items-center justify-between">
<Label>Technical Specifications</Label>
<Button type="button" variant="outline" size="sm" onClick={addSpec}>
<Plus className="h-4 w-4 mr-2" />
Add Specification
</Button>
</div>
{specs.length === 0 ? (
<Card className="p-6 text-center text-muted-foreground">
No specifications added yet. Click "Add Specification" to get started.
</Card>
) : (
<div className="space-y-3">
{specs.map((spec, index) => (
<Card key={index} className="p-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-3">
<div className="lg:col-span-2">
<Label className="text-xs">Specification Name</Label>
<Input
value={spec.spec_name}
onChange={(e) => updateSpec(index, 'spec_name', e.target.value)}
placeholder="e.g., Track Material"
list={`common-specs-${index}`}
/>
{commonSpecs.length > 0 && (
<datalist id={`common-specs-${index}`}>
{commonSpecs.map(s => <option key={s} value={s} />)}
</datalist>
)}
</div>
<div>
<Label className="text-xs">Value</Label>
<Input
value={getDisplayValue(spec)}
onChange={(e) => {
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' && (
<p className="text-xs text-muted-foreground mt-1">
Enter in {getDisplayUnit(spec.unit, preferences.measurement_system)}
</p>
)}
</div>
<div>
<Label className="text-xs">Type</Label>
<Select
value={spec.spec_type}
onValueChange={(value) => updateSpec(index, 'spec_type', value)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="string">Text</SelectItem>
<SelectItem value="number">Number</SelectItem>
<SelectItem value="boolean">Yes/No</SelectItem>
<SelectItem value="date">Date</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label className="text-xs">Category</Label>
<Select
value={spec.category || ''}
onValueChange={(value) => updateSpec(index, 'category', value)}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
{categories.map(cat => (
<SelectItem key={cat} value={cat}>{cat}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-end gap-2">
<div className="flex-1">
<Label className="text-xs">Unit</Label>
<Input
value={spec.unit || ''}
onChange={(e) => updateSpec(index, 'unit', e.target.value)}
placeholder="Unit"
list={`units-${index}`}
className={unitErrors[index] ? 'border-destructive' : ''}
/>
<datalist id={`units-${index}`}>
{METRIC_UNITS.map(u => <option key={u} value={u} />)}
</datalist>
{unitErrors[index] && (
<p className="text-xs text-destructive mt-1">{unitErrors[index]}</p>
)}
<p className="text-xs text-muted-foreground mt-1">
Metric units only
</p>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeSpec(index)}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
</div>
</Card>
))}
</div>
)}
</div>
);
}
/**
* 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 };
}