diff --git a/backend/__pycache__/db_utils.cpython-313.pyc b/backend/__pycache__/db_utils.cpython-313.pyc index feab553..dfc8c39 100644 Binary files a/backend/__pycache__/db_utils.cpython-313.pyc and b/backend/__pycache__/db_utils.cpython-313.pyc differ diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index 5140ab0..4a21043 100644 Binary files a/backend/__pycache__/main.cpython-313.pyc and b/backend/__pycache__/main.cpython-313.pyc differ diff --git a/backend/db_utils.py b/backend/db_utils.py index 03f3c2b..c5ba8ba 100644 --- a/backend/db_utils.py +++ b/backend/db_utils.py @@ -55,7 +55,7 @@ def list_recipes_db() -> List[Dict[str, Any]]: cur.execute( """ SELECT id, name, meal_type, time_minutes, - tags, ingredients, steps, image + tags, ingredients, steps, image, made_by FROM recipes ORDER BY id """ @@ -68,7 +68,7 @@ def list_recipes_db() -> List[Dict[str, Any]]: def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Dict[str, Any]]: """ עדכון מתכון קיים לפי id. - recipe_data: name, meal_type, time_minutes, tags, ingredients, steps, image + recipe_data: name, meal_type, time_minutes, tags, ingredients, steps, image, made_by """ conn = get_conn() try: @@ -82,9 +82,10 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di tags = %s, ingredients = %s, steps = %s, - image = %s + image = %s, + made_by = %s WHERE id = %s - RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image + RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image, made_by """, ( recipe_data["name"], @@ -94,6 +95,7 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di json.dumps(recipe_data.get("ingredients", [])), json.dumps(recipe_data.get("steps", [])), recipe_data.get("image"), + recipe_data.get("made_by"), recipe_id, ), ) @@ -131,9 +133,9 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]: with conn.cursor() as cur: cur.execute( """ - INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps, image) - VALUES (%s, %s, %s, %s, %s, %s, %s) - RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image + INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps, image, made_by) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image, made_by """, ( recipe_data["name"], @@ -143,6 +145,7 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]: json.dumps(recipe_data.get("ingredients", [])), json.dumps(recipe_data.get("steps", [])), recipe_data.get("image"), + recipe_data.get("made_by"), ), ) row = cur.fetchone() @@ -160,7 +163,7 @@ def get_recipes_by_filters_db( try: query = """ SELECT id, name, meal_type, time_minutes, - tags, ingredients, steps + tags, ingredients, steps, image, made_by FROM recipes WHERE 1=1 """ diff --git a/backend/main.py b/backend/main.py index 6328b3c..74ac977 100644 --- a/backend/main.py +++ b/backend/main.py @@ -21,6 +21,7 @@ class RecipeBase(BaseModel): name: str meal_type: str # breakfast / lunch / dinner / snack time_minutes: int + made_by: Optional[str] = None # Person who created this recipe version tags: List[str] = [] ingredients: List[str] = [] steps: List[str] = [] @@ -63,6 +64,7 @@ def list_recipes(): name=r["name"], meal_type=r["meal_type"], time_minutes=r["time_minutes"], + made_by=r.get("made_by"), tags=r["tags"] or [], ingredients=r["ingredients"] or [], steps=r["steps"] or [], @@ -85,6 +87,7 @@ def create_recipe(recipe_in: RecipeCreate): name=row["name"], meal_type=row["meal_type"], time_minutes=row["time_minutes"], + made_by=row.get("made_by"), tags=row["tags"] or [], ingredients=row["ingredients"] or [], steps=row["steps"] or [], @@ -105,6 +108,7 @@ def update_recipe(recipe_id: int, recipe_in: RecipeUpdate): name=row["name"], meal_type=row["meal_type"], time_minutes=row["time_minutes"], + made_by=row.get("made_by"), tags=row["tags"] or [], ingredients=row["ingredients"] or [], steps=row["steps"] or [], @@ -137,6 +141,7 @@ def random_recipe( name=r["name"], meal_type=r["meal_type"], time_minutes=r["time_minutes"], + made_by=r.get("made_by"), tags=r["tags"] or [], ingredients=r["ingredients"] or [], steps=r["steps"] or [], diff --git a/backend/schema.sql b/backend/schema.sql index 905d5c4..aa7547c 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS recipes ( name TEXT NOT NULL, meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack time_minutes INTEGER NOT NULL, - + made_by TEXT, -- Person who created this recipe version tags JSONB NOT NULL DEFAULT '[]', -- ["מהיר", "בריא"] ingredients JSONB NOT NULL DEFAULT '[]', -- ["ביצה", "עגבניה", "מלח"] steps JSONB NOT NULL DEFAULT '[]', -- ["לחתוך", "לבשל", ...] @@ -18,8 +18,12 @@ CREATE INDEX IF NOT EXISTS idx_recipes_meal_type CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes ON recipes (time_minutes); +CREATE INDEX IF NOT EXISTS idx_recipes_made_by + ON recipes (made_by); + CREATE INDEX IF NOT EXISTS idx_recipes_tags_jsonb ON recipes USING GIN (tags); CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb ON recipes USING GIN (ingredients); + diff --git a/frontend/src/App.css b/frontend/src/App.css index 6af280a..8f706a8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -314,6 +314,13 @@ select { color: var(--text-muted); } +.recipe-made-by { + margin: 0.3rem 0 0; + font-size: 0.8rem; + color: var(--accent); + font-weight: 500; +} + .pill-row { display: flex; flex-wrap: wrap; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 10e6a3b..5d61159 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,6 +19,7 @@ function App() { const [filterMealType, setFilterMealType] = useState(""); const [filterMaxTime, setFilterMaxTime] = useState(""); const [filterTags, setFilterTags] = useState([]); + const [filterMadeBy, setFilterMadeBy] = useState(""); // Random recipe filters const [mealTypeFilter, setMealTypeFilter] = useState(""); @@ -95,6 +96,11 @@ function App() { } } + // Filter by made_by + if (filterMadeBy && (!recipe.made_by || recipe.made_by !== filterMadeBy)) { + return false; + } + return true; }); }; @@ -222,6 +228,8 @@ function App() { onMaxTimeChange={setFilterMaxTime} filterTags={filterTags} onTagsChange={setFilterTags} + filterMadeBy={filterMadeBy} + onMadeByChange={setFilterMadeBy} /> diff --git a/frontend/src/components/RecipeDetails.jsx b/frontend/src/components/RecipeDetails.jsx index 5c0d00e..402a58d 100644 --- a/frontend/src/components/RecipeDetails.jsx +++ b/frontend/src/components/RecipeDetails.jsx @@ -26,6 +26,9 @@ function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal }

{translateMealType(recipe.meal_type)} · {recipe.time_minutes} דקות הכנה

+ {recipe.made_by && ( +

המתכון של: {recipe.made_by}

+ )}
⏱ {recipe.time_minutes} דק׳ diff --git a/frontend/src/components/RecipeFormDrawer.jsx b/frontend/src/components/RecipeFormDrawer.jsx index 618c447..98f32c0 100644 --- a/frontend/src/components/RecipeFormDrawer.jsx +++ b/frontend/src/components/RecipeFormDrawer.jsx @@ -4,6 +4,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { const [name, setName] = useState(""); const [mealType, setMealType] = useState("lunch"); const [timeMinutes, setTimeMinutes] = useState(15); + const [madeBy, setMadeBy] = useState(""); const [tags, setTags] = useState(""); const [image, setImage] = useState(""); @@ -18,6 +19,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { setName(editingRecipe.name || ""); setMealType(editingRecipe.meal_type || "lunch"); setTimeMinutes(editingRecipe.time_minutes || 15); + setMadeBy(editingRecipe.made_by || ""); setTags((editingRecipe.tags || []).join(", ")); setImage(editingRecipe.image || ""); setIngredients(editingRecipe.ingredients || [""]); @@ -26,6 +28,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { setName(""); setMealType("lunch"); setTimeMinutes(15); + setMadeBy(""); setTags(""); setImage(""); setIngredients([""]); @@ -94,6 +97,10 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { steps: cleanSteps, }; + if (madeBy.trim()) { + payload.made_by = madeBy.trim(); + } + if (image) { payload.image = image; } @@ -145,6 +152,15 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
+
+ + setMadeBy(e.target.value)} + placeholder="שם האדם שיצר את הגרסה הזו..." + /> +
+
diff --git a/frontend/src/components/RecipeSearchList.jsx b/frontend/src/components/RecipeSearchList.jsx index 55ca5f2..5c60610 100644 --- a/frontend/src/components/RecipeSearchList.jsx +++ b/frontend/src/components/RecipeSearchList.jsx @@ -14,6 +14,8 @@ function RecipeSearchList({ onMaxTimeChange, filterTags, onTagsChange, + filterMadeBy, + onMadeByChange, }) { const [expandFilters, setExpandFilters] = useState(false); @@ -25,6 +27,9 @@ function RecipeSearchList({ // Extract unique meal types from ALL recipes (not filtered) const mealTypes = Array.from(new Set(allRecipes.map((r) => r.meal_type))).sort(); + // Extract unique made_by from ALL recipes (not filtered) + const allMadeBy = Array.from(new Set(allRecipes.map((r) => r.made_by).filter(Boolean))).sort(); + // Extract max time for slider from ALL recipes (not filtered) - add 10 to make it comfortable to select max time recipes const maxTimeInRecipes = Math.max(...allRecipes.map((r) => r.time_minutes), 0); const sliderMax = maxTimeInRecipes > 0 ? maxTimeInRecipes + 10 : 70; @@ -42,9 +47,10 @@ function RecipeSearchList({ onMealTypeChange(""); onMaxTimeChange(""); onTagsChange([]); + onMadeByChange(""); }; - const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0; + const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterMadeBy; return (
@@ -152,6 +158,30 @@ function RecipeSearchList({
)} + + {/* Made By Filter */} + {allMadeBy.length > 0 && ( +
+ +
+ + {allMadeBy.map((person) => ( + + ))} +
+
+ )} {/* Clear All Button */} diff --git a/my-recipes-chart/templates/db-schema-configmap.yaml b/my-recipes-chart/templates/db-schema-configmap.yaml new file mode 100644 index 0000000..9b901c9 --- /dev/null +++ b/my-recipes-chart/templates/db-schema-configmap.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-schema + namespace: {{ .Values.global.namespace }} +data: + schema.sql: | + -- Create recipes table + CREATE TABLE IF NOT EXISTS recipes ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + meal_type TEXT NOT NULL, + time_minutes INTEGER NOT NULL, + made_by TEXT, + tags JSONB NOT NULL DEFAULT '[]', + ingredients JSONB NOT NULL DEFAULT '[]', + steps JSONB NOT NULL DEFAULT '[]', + image TEXT + ); + + CREATE INDEX IF NOT EXISTS idx_recipes_meal_type + ON recipes (meal_type); + + CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes + ON recipes (time_minutes); + + CREATE INDEX IF NOT EXISTS idx_recipes_made_by + ON recipes (made_by); + + CREATE INDEX IF NOT EXISTS idx_recipes_tags_jsonb + ON recipes USING GIN (tags); + + CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb + ON recipes USING GIN (ingredients); +