File size: 7,010 Bytes
be02369 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
import React, { useState, useMemo } from 'react';
import { BeakerIcon } from '../components/icons';
type WaterUnit = 'L' | 'gal';
type ConcentrateUnit = 'ml' | 'tsp' | 'tbsp';
type Strength = 'full' | 'half' | 'quarter';
const STRENGTH_MULTIPLIERS: Record<Strength, number> = {
full: 1,
half: 0.5,
quarter: 0.25,
};
const CONVERSION_TO_ML = {
ml: 1,
tsp: 4.92892,
tbsp: 14.7868,
};
const CONVERSION_TO_LITER = {
L: 1,
gal: 3.78541,
};
const FertilizerMixerView: React.FC = () => {
const [waterAmount, setWaterAmount] = useState(1);
const [waterUnit, setWaterUnit] = useState<WaterUnit>('gal');
const [concentrateAmount, setConcentrateAmount] = useState(1);
const [concentrateUnit, setConcentrateUnit] = useState<ConcentrateUnit>('tsp');
const [strength, setStrength] = useState<Strength>('half');
const [baseWaterAmount, setBaseWaterAmount] = useState(1);
const [baseWaterUnit, setBaseWaterUnit] = useState<WaterUnit>('gal');
const result = useMemo(() => {
try {
const desiredWaterL = waterAmount * CONVERSION_TO_LITER[waterUnit];
const baseWaterL = baseWaterAmount * CONVERSION_TO_LITER[baseWaterUnit];
const baseConcentrateMl = concentrateAmount * CONVERSION_TO_ML[concentrateUnit];
if (baseWaterL === 0) return { amount: 0, unit: 'ml' };
const ratio = baseConcentrateMl / baseWaterL; // ml of concentrate per L of water
const strengthMultiplier = STRENGTH_MULTIPLIERS[strength];
const neededMl = desiredWaterL * ratio * strengthMultiplier;
// Return result in a user-friendly unit
if (neededMl < 15 && concentrateUnit !== 'ml') return { amount: neededMl / CONVERSION_TO_ML.tsp, unit: 'tsp' };
if (neededMl < 45 && concentrateUnit !== 'ml') return { amount: neededMl / CONVERSION_TO_ML.tbsp, unit: 'tbsp' };
return { amount: neededMl, unit: 'ml' };
} catch (e) {
return { amount: 0, unit: 'ml' };
}
}, [waterAmount, waterUnit, concentrateAmount, concentrateUnit, strength, baseWaterAmount, baseWaterUnit]);
const renderUnitSelector = (value: string, setter: (val: any) => void, units: string[]) => (
<div className="flex gap-1 bg-stone-100 p-1 rounded-lg">
{units.map(unit => (
<button key={unit} onClick={() => setter(unit)} className={`px-3 py-1 text-sm font-medium rounded-md ${value === unit ? 'bg-white shadow' : ''}`}>
{unit}
</button>
))}
</div>
);
return (
<div className="space-y-8 max-w-2xl mx-auto">
<header className="text-center">
<h2 className="text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl flex items-center justify-center gap-3">
<BeakerIcon className="w-8 h-8 text-cyan-600" />
Fertilizer Mixer
</h2>
<p className="mt-4 text-lg leading-8 text-stone-600">
Calculate the perfect fertilizer dilution every time. No more over or under-feeding.
</p>
</header>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
<div className="bg-white p-6 rounded-xl shadow-lg border border-stone-200 space-y-6">
<div>
<h3 className="font-semibold text-stone-800 mb-2">1. Dosage from Bottle</h3>
<p className="text-xs text-stone-500 mb-2">Enter the recommended full-strength dose from the fertilizer label.</p>
<div className="flex items-center gap-2">
<input type="number" value={concentrateAmount} onChange={e => setConcentrateAmount(Number(e.target.value))} className="w-full p-2 border rounded-md" />
{renderUnitSelector(concentrateUnit, setConcentrateUnit, ['ml', 'tsp', 'tbsp'])}
</div>
<p className="text-center my-1 text-stone-600 font-medium">per</p>
<div className="flex items-center gap-2">
<input type="number" value={baseWaterAmount} onChange={e => setBaseWaterAmount(Number(e.target.value))} className="w-full p-2 border rounded-md" />
{renderUnitSelector(baseWaterUnit, setBaseWaterUnit, ['L', 'gal'])}
</div>
</div>
<div>
<h3 className="font-semibold text-stone-800 mb-2">2. Your Watering Can</h3>
<p className="text-xs text-stone-500 mb-2">How much water are you preparing?</p>
<div className="flex items-center gap-2">
<input type="number" value={waterAmount} onChange={e => setWaterAmount(Number(e.target.value))} className="w-full p-2 border rounded-md" />
{renderUnitSelector(waterUnit, setWaterUnit, ['L', 'gal'])}
</div>
</div>
<div>
<h3 className="font-semibold text-stone-800 mb-2">3. Desired Strength</h3>
<div className="flex gap-1 bg-stone-100 p-1 rounded-lg">
<button onClick={() => setStrength('full')} className={`flex-1 py-2 text-sm font-medium rounded-md ${strength === 'full' ? 'bg-white shadow' : ''}`}>Full (100%)</button>
<button onClick={() => setStrength('half')} className={`flex-1 py-2 text-sm font-medium rounded-md ${strength === 'half' ? 'bg-white shadow' : ''}`}>Half (50%)</button>
<button onClick={() => setStrength('quarter')} className={`flex-1 py-2 text-sm font-medium rounded-md ${strength === 'quarter' ? 'bg-white shadow' : ''}`}>Quarter (25%)</button>
</div>
</div>
</div>
<div className="bg-white p-6 rounded-xl shadow-lg border-2 border-cyan-600 space-y-4 text-center">
<h3 className="text-xl font-bold text-stone-900">Your Custom Mix</h3>
<p className="text-stone-600">Add this much concentrate to your watering can:</p>
<div>
<p className="text-5xl font-bold text-cyan-700">{result.amount.toFixed(2)}</p>
<p className="text-lg text-stone-600">{result.unit}</p>
</div>
<div className="mt-4 pt-4 border-t-2 border-dashed">
<p className="text-sm text-stone-700">
This creates a {strength}-strength solution of {waterAmount} {waterUnit} of fertilizer.
</p>
</div>
</div>
</div>
</div>
);
};
export default FertilizerMixerView;
|