Costing App

import React, { useState } from ‘react’;
import { Plus, Trash2, Calculator, Download, Package } from ‘lucide-react’;

export default function JerkyCostingApp() {
const [view, setView] = useState(‘ingredients’);
const [ingredients, setIngredients] = useState([]);
const [recipes, setRecipes] = useState([]);
const [selectedRecipe, setSelectedRecipe] = useState(null);

// Ingredient form state
const [newIngredient, setNewIngredient] = useState({
name: ”,
weight: ”,
unit: ‘lb’,
costPerUnit: ”,
totalPaid: ”,
supplier: ”
});

// Recipe form state
const [newRecipe, setNewRecipe] = useState({
name: ”,
batchSize: ”,
items: []
});

const [recipeItem, setRecipeItem] = useState({
ingredientId: ”,
quantity: ”
});

// Add ingredient
const handleAddIngredient = () => {
if (newIngredient.name && newIngredient.weight && newIngredient.costPerUnit && newIngredient.totalPaid) {
const ingredient = {
id: Date.now(),
…newIngredient,
weight: parseFloat(newIngredient.weight),
costPerUnit: parseFloat(newIngredient.costPerUnit),
totalPaid: parseFloat(newIngredient.totalPaid)
};
setIngredients([…ingredients, ingredient]);
setNewIngredient({ name: ”, weight: ”, unit: ‘lb’, costPerUnit: ”, totalPaid: ”, supplier: ” });
}
};

// Add recipe item
const handleAddRecipeItem = () => {
if (recipeItem.ingredientId && recipeItem.quantity) {
setNewRecipe({
…newRecipe,
items: […newRecipe.items, {
ingredientId: recipeItem.ingredientId,
quantity: parseFloat(recipeItem.quantity)
}]
});
setRecipeItem({ ingredientId: ”, quantity: ” });
}
};

// Save recipe
const handleSaveRecipe = () => {
if (newRecipe.name && newRecipe.batchSize && newRecipe.items.length > 0) {
const recipe = {
id: Date.now(),
…newRecipe,
batchSize: parseFloat(newRecipe.batchSize)
};
setRecipes([…recipes, recipe]);
setNewRecipe({ name: ”, batchSize: ”, items: [] });
}
};

// Calculate recipe cost
const calculateRecipeCost = (recipe) => {
return recipe.items.reduce((total, item) => {
const ingredient = ingredients.find(ing => ing.id === item.ingredientId);
if (ingredient) {
return total + (ingredient.costPerUnit * item.quantity);
}
return total;
}, 0);
};

// Export to Notion
const handleExportToNotion = async (recipe) => {
const totalCost = calculateRecipeCost(recipe);
const costPerUnit = totalCost / recipe.batchSize;

const notionData = {
parent: { database_id: “YOUR_DATABASE_ID” },
properties: {
“Recipe Name”: { title: [{ text: { content: recipe.name } }] },
“Batch Size”: { number: recipe.batchSize },
“Total Cost”: { number: parseFloat(totalCost.toFixed(2)) },
“Cost Per Unit”: { number: parseFloat(costPerUnit.toFixed(2)) }
}
};

alert(‘To enable Notion export:\n1. Create a Notion integration at notion.so/my-integrations\n2. Create a database in Notion\n3. Share the database with your integration\n4. Replace YOUR_DATABASE_ID in the code with your database ID\n\nData to export:\n’ + JSON.stringify(notionData, null, 2));
};

return (

Beef Jerky Recipe Costing

Track ingredients, manage recipes, and calculate costs

{/* Navigation */}



{/* Ingredients View */}
{view === ‘ingredients’ && (

Add Ingredient


setNewIngredient({…newIngredient, name: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”e.g., Beef Eye Round”
/>

setNewIngredient({…newIngredient, weight: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”10″
/>


setNewIngredient({…newIngredient, costPerUnit: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”4.99″
/>

setNewIngredient({…newIngredient, totalPaid: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”49.90″
/>

setNewIngredient({…newIngredient, supplier: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”e.g., Costco”
/>

Ingredient List

{ingredients.length === 0 ? (

No ingredients added yet

) : (

{ingredients.map((ing) => (

))}

Ingredient Amount Cost/Unit Total Paid Supplier Action
{ing.name} {ing.weight} {ing.unit} ${ing.costPerUnit.toFixed(2)} ${ing.totalPaid.toFixed(2)} {ing.supplier || ‘-‘}

)}

)}

{/* Recipes View */}
{view === ‘recipes’ && (

Create Recipe


setNewRecipe({…newRecipe, name: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”e.g., Teriyaki Beef Jerky”
/>

setNewRecipe({…newRecipe, batchSize: e.target.value})}
className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”e.g., 100 (bags/pieces)”
/>

Add Ingredients


setRecipeItem({…recipeItem, quantity: e.target.value})}
className=”w-24 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent”
placeholder=”Qty”
/>

{newRecipe.items.length > 0 && (

Recipe Card

{newRecipe.name || ‘Untitled Recipe’}

Makes: {newRecipe.batchSize || ‘—’} units

Ingredients:

    {newRecipe.items.map((item, idx) => {
    const ing = ingredients.find(i => i.id === item.ingredientId);
    return (

  • {ing?.name}

    {item.quantity} {ing?.unit}
  • );
    })}

)}

Saved Recipes

{recipes.length === 0 ? (

No recipes created yet

) : (
recipes.map((recipe) => {
const totalCost = calculateRecipeCost(recipe);
const costPerUnit = totalCost / recipe.batchSize;
return (

{recipe.name}

Batch Size: {recipe.batchSize} units
Total Cost: ${totalCost.toFixed(2)}
Cost Per Unit: ${costPerUnit.toFixed(2)}
{recipe.items.length} ingredient(s)

);
})
)}

)}

{/* Calculator View */}
{view === ‘calculator’ && (

Cost Calculator & Scenario Planning

{recipes.length === 0 ? (

Create a recipe first to use the calculator

) : (
<>


{selectedRecipe && (

{selectedRecipe.name}

Batch Size

setSelectedRecipe({
…selectedRecipe,
batchSize: parseFloat(e.target.value) || 0
})}
className=”mt-1 w-full px-3 py-2 border border-gray-300 rounded text-center font-bold”
/>

Total Cost
${calculateRecipeCost(selectedRecipe).toFixed(2)}

Cost Per Unit
${(calculateRecipeCost(selectedRecipe) / selectedRecipe.batchSize).toFixed(2)}

Ingredient Breakdown

{selectedRecipe.items.map((item, idx) => {
const ing = ingredients.find(i => i.id === item.ingredientId);
if (!ing) return null;
const itemCost = ing.costPerUnit * item.quantity;
return (

);
})}

Ingredient Quantity Cost/Unit Line Total
{ing.name} {
const newItems = […selectedRecipe.items];
newItems[idx].quantity = parseFloat(e.target.value) || 0;
setSelectedRecipe({…selectedRecipe, items: newItems});
}}
className=”w-24 px-2 py-1 border border-gray-300 rounded text-right”
/>
{ing.unit}
${ing.costPerUnit.toFixed(2)}/{ing.unit} ${itemCost.toFixed(2)}
Total Recipe Cost: ${calculateRecipeCost(selectedRecipe).toFixed(2)}

)}

)}

)}

);
}

Scroll to Top