Iapetus / frontend /src /App.jsx
EphAsad's picture
Upload 9972 files
01fdca8 verified
Raw
History Blame Contribute Delete
4.04 kB
import { useRef, useState } from "react";
import { fetchFullReport } from "./api";
import { DecisionCard } from "./components/DecisionCard";
import { ExportButton } from "./components/ExportButton";
import { GrowthCurveChart } from "./components/GrowthCurveChart";
import { LoadingSpinner } from "./components/LoadingSpinner";
import { RiskDriversCard } from "./components/RiskDriversCard";
import { ScenarioForm } from "./components/ScenarioForm";
import { SummaryCard } from "./components/SummaryCard";
import "./styles/app.css";
const defaultForm = {
product_category: "deli_salad",
intended_use: "ready_to_eat",
pathogen: "Listeria monocytogenes",
ph: 5.0,
aw: 0.972,
salt_percent: 2.1,
sugar_percent: 3.2,
fat_percent: 7.1,
preservative_flag: false,
preservative_type: "none",
acidulant_type: "vinegar",
packaging_type: "tub",
oxygen_condition: "aerobic",
storage_temperature_c: 4,
inoculation_type: "low_inoculum",
initial_inoculum_log_cfu_g: 1.06,
target_shelf_life_days: 21,
curve_mode: "both",
};
const numberFields = new Set([
"ph",
"aw",
"salt_percent",
"sugar_percent",
"fat_percent",
"storage_temperature_c",
"initial_inoculum_log_cfu_g",
"target_shelf_life_days",
]);
export default function App() {
const [formData, setFormData] = useState(defaultForm);
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const chartRef = useRef(null);
function handleChange(event) {
const { name, value } = event.target;
let nextValue = value;
if (name === "preservative_flag") {
nextValue = value === "true";
} else if (numberFields.has(name)) {
nextValue = value === "" ? "" : Number(value);
}
setFormData((current) => ({ ...current, [name]: nextValue }));
}
async function handleSubmit(event) {
event.preventDefault();
setLoading(true);
setError("");
try {
const payload = {
...formData,
target_shelf_life_days: Number(formData.target_shelf_life_days),
};
const response = await fetchFullReport(payload);
setResult(response);
} catch (submitError) {
setError(submitError.message);
} finally {
setLoading(false);
}
}
return (
<div className="app-shell">
<header className="hero">
<div>
<p className="eyebrow">Iapetus</p>
<h1>Shelf-Life Risk Engine</h1>
<p className="hero-copy">
Synthetic-first predictive microbiology for challenge-test style shelf-life simulation.
</p>
</div>
</header>
<main className="layout">
<section className="panel">
<div className="card">
<h2>Scenario Input</h2>
<ScenarioForm formData={formData} onChange={handleChange} onSubmit={handleSubmit} loading={loading} />
</div>
</section>
<section className="panel results-panel">
{loading && <LoadingSpinner />}
{error && <div className="card error-card">{error}</div>}
{!loading && !error && result && (
<>
<GrowthCurveChart result={result} ref={chartRef} />
<DecisionCard decision={result.decision} />
<RiskDriversCard drivers={result.sensitivity_analysis.drivers} />
<SummaryCard
summary={result.summary}
uncertaintyDrivers={result.monte_carlo.uncertainty_drivers}
summaryProvider={result.summary_provider}
fallbackUsed={result.summary_fallback_used}
/>
<ExportButton result={result} scenario={formData} chartRef={chartRef} />
</>
)}
{!loading && !result && (
<div className="card empty-state">
Run a scenario to see the predicted growth curve, uncertainty band, and shelf-life recommendation.
</div>
)}
</section>
</main>
</div>
);
}