import React, { useState } from 'react'; import type { ShippingRate, WeightTier, OrderTotalRule, LocationType, WeightCalcType } from '@/types/shipping.types'; interface RateFormProps { rate: ShippingRate | null; locationType: LocationType; locationCode: string; locationName?: string; onSave: (rate: Partial) => void; onCancel: () => void; isSaving: boolean; } const LOCATION_TYPE_LABELS: Record = { province: 'Province/City', district: 'District', ward: 'Ward/Commune/Town', region: 'Region', }; export const RateForm: React.FC = ({ rate, locationType, locationCode, locationName, onSave, onCancel, isSaving, }) => { const [baseRate, setBaseRate] = useState(rate?.base_rate ?? 0); const [priority, setPriority] = useState(rate?.priority ?? 0); const [weightCalcType, setWeightCalcType] = useState( rate?.weight_calc_type ?? 'replace' ); const [weightTiers, setWeightTiers] = useState( rate?.weight_tiers?.length ? rate.weight_tiers : [] ); const [orderRules, setOrderRules] = useState( rate?.order_total_rules ?? [] ); // ---- Weight tier helpers -------------------------------------------------- const addWeightTier = () => setWeightTiers([...weightTiers, { min: 0, max: 0, price: 0 }]); const removeWeightTier = (i: number) => setWeightTiers(weightTiers.filter((_, idx) => idx !== i)); const updateWeightTier = (i: number, field: keyof WeightTier, value: number) => { const next = [...weightTiers]; next[i] = { ...next[i], [field]: value }; setWeightTiers(next); }; // ---- Order rule helpers --------------------------------------------------- const addOrderRule = () => setOrderRules([...orderRules, { min_total: 0, max_total: 0, shipping_fee: 0 }]); const removeOrderRule = (i: number) => setOrderRules(orderRules.filter((_, idx) => idx !== i)); const updateOrderRule = (i: number, field: keyof OrderTotalRule, value: number) => { const next = [...orderRules]; next[i] = { ...next[i], [field]: value }; setOrderRules(next); }; // ---- Submit --------------------------------------------------------------- const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSave({ id: rate?.id, location_type: locationType, location_code: locationCode, base_rate: baseRate, priority, weight_calc_type: weightCalcType, weight_tiers: weightTiers, order_total_rules: orderRules, }); }; // ---- Styles (inline, no external deps) ----------------------------------- const inputStyle: React.CSSProperties = { borderRadius: '4px', border: '1px solid #8c8f94', padding: '4px 8px', }; const tileStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px', flexWrap: 'wrap', }; const descStyle: React.CSSProperties = { fontSize: '12px', color: '#6b7280', marginTop: '4px', }; const segmentStyle = (active: boolean): React.CSSProperties => ({ padding: '5px 14px', border: '1px solid', borderColor: active ? '#2271b1' : '#c3c4c7', borderRadius: active ? '4px' : '4px', background: active ? '#2271b1' : '#fff', color: active ? '#fff' : '#1d2327', cursor: 'pointer', fontSize: '13px', fontWeight: active ? 600 : 400, transition: 'all 0.1s', }); return (

{rate ? 'Edit shipping rate' : 'Add shipping rate'} {locationName && ( — {LOCATION_TYPE_LABELS[locationType]}: {locationName} )}

{/* Base rate */} {/* Priority */} {/* Weight calculation mode */} {/* Weight tiers */} {/* Order total rules */}
setBaseRate(Number(e.target.value))} min="0" step="1000" required style={{ ...inputStyle, width: '180px' }} />

Default rate when no weight tier matches.

setPriority(Number(e.target.value))} min="0" style={{ ...inputStyle, width: '100px' }} />

Higher numbers win when multiple rules match the same location.

{weightCalcType === 'replace' ? 'Weight tiers replace the base rate completely.' : 'Calculated: base rate + (weight × tier price per kg).'}

{weightTiers.length === 0 && (

No tiers — base rate always applies.

)} {weightTiers.map((tier, i) => (
#{i + 1} From updateWeightTier(i, 'min', Number(e.target.value))} min="0" step="0.1" placeholder="0" style={{ ...inputStyle, width: '90px' }} /> kg — to updateWeightTier(i, 'max', Number(e.target.value))} min="0" step="0.1" placeholder="0 = ∞" style={{ ...inputStyle, width: '90px' }} title="0 = unlimited" /> kg updateWeightTier(i, 'price', Number(e.target.value))} min="0" step="1000" placeholder={weightCalcType === 'per_kg' ? 'VND/kg' : 'VND'} style={{ ...inputStyle, width: '130px' }} /> {weightCalcType === 'per_kg' ? 'VND/kg' : 'VND'}
))}
{orderRules.length === 0 && (

No conditions — weight rate (or base rate) always applies.

)} {orderRules.map((rule, i) => (
#{i + 1} Order from updateOrderRule(i, 'min_total', Number(e.target.value))} min="0" step="1000" placeholder="0" style={{ ...inputStyle, width: '140px' }} /> VND — to updateOrderRule(i, 'max_total', Number(e.target.value))} min="0" step="1000" placeholder="0 = ∞" style={{ ...inputStyle, width: '140px' }} title="0 = unlimited" /> VND → shipping fee updateOrderRule(i, 'shipping_fee', Number(e.target.value))} min="0" step="1000" placeholder="0 = free" style={{ ...inputStyle, width: '140px' }} /> VND
))}

The first matching condition (in order) applies. Set 0 in the "to" field for no upper limit.

); };