Yuki / views /FertilizerMixerView.tsx
Severian's picture
Upload 43 files
be02369 verified
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;