Add small pic to recipes and update the scehma.sql
This commit is contained in:
parent
984e10682b
commit
02e4a5d7fa
Binary file not shown.
Binary file not shown.
@ -55,7 +55,7 @@ def list_recipes_db() -> List[Dict[str, Any]]:
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id, name, meal_type, time_minutes,
|
SELECT id, name, meal_type, time_minutes,
|
||||||
tags, ingredients, steps
|
tags, ingredients, steps, image
|
||||||
FROM recipes
|
FROM recipes
|
||||||
ORDER BY id
|
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]]:
|
def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
עדכון מתכון קיים לפי id.
|
עדכון מתכון קיים לפי id.
|
||||||
recipe_data: name, meal_type, time_minutes, tags, ingredients, steps
|
recipe_data: name, meal_type, time_minutes, tags, ingredients, steps, image
|
||||||
"""
|
"""
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
try:
|
try:
|
||||||
@ -81,9 +81,10 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di
|
|||||||
time_minutes = %s,
|
time_minutes = %s,
|
||||||
tags = %s,
|
tags = %s,
|
||||||
ingredients = %s,
|
ingredients = %s,
|
||||||
steps = %s
|
steps = %s,
|
||||||
|
image = %s
|
||||||
WHERE id = %s
|
WHERE id = %s
|
||||||
RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps
|
RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
recipe_data["name"],
|
recipe_data["name"],
|
||||||
@ -92,6 +93,7 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di
|
|||||||
json.dumps(recipe_data.get("tags", [])),
|
json.dumps(recipe_data.get("tags", [])),
|
||||||
json.dumps(recipe_data.get("ingredients", [])),
|
json.dumps(recipe_data.get("ingredients", [])),
|
||||||
json.dumps(recipe_data.get("steps", [])),
|
json.dumps(recipe_data.get("steps", [])),
|
||||||
|
recipe_data.get("image"),
|
||||||
recipe_id,
|
recipe_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -129,9 +131,9 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps)
|
INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps, image)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||||
RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps
|
RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
recipe_data["name"],
|
recipe_data["name"],
|
||||||
@ -140,6 +142,7 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
json.dumps(recipe_data.get("tags", [])),
|
json.dumps(recipe_data.get("tags", [])),
|
||||||
json.dumps(recipe_data.get("ingredients", [])),
|
json.dumps(recipe_data.get("ingredients", [])),
|
||||||
json.dumps(recipe_data.get("steps", [])),
|
json.dumps(recipe_data.get("steps", [])),
|
||||||
|
recipe_data.get("image"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class RecipeBase(BaseModel):
|
|||||||
tags: List[str] = []
|
tags: List[str] = []
|
||||||
ingredients: List[str] = []
|
ingredients: List[str] = []
|
||||||
steps: List[str] = []
|
steps: List[str] = []
|
||||||
|
image: Optional[str] = None # Base64-encoded image or image URL
|
||||||
|
|
||||||
|
|
||||||
class RecipeCreate(RecipeBase):
|
class RecipeCreate(RecipeBase):
|
||||||
@ -65,6 +66,7 @@ def list_recipes():
|
|||||||
tags=r["tags"] or [],
|
tags=r["tags"] or [],
|
||||||
ingredients=r["ingredients"] or [],
|
ingredients=r["ingredients"] or [],
|
||||||
steps=r["steps"] or [],
|
steps=r["steps"] or [],
|
||||||
|
image=r.get("image"),
|
||||||
)
|
)
|
||||||
for r in rows
|
for r in rows
|
||||||
]
|
]
|
||||||
@ -86,6 +88,7 @@ def create_recipe(recipe_in: RecipeCreate):
|
|||||||
tags=row["tags"] or [],
|
tags=row["tags"] or [],
|
||||||
ingredients=row["ingredients"] or [],
|
ingredients=row["ingredients"] or [],
|
||||||
steps=row["steps"] or [],
|
steps=row["steps"] or [],
|
||||||
|
image=row.get("image"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.put("/recipes/{recipe_id}", response_model=Recipe)
|
@app.put("/recipes/{recipe_id}", response_model=Recipe)
|
||||||
@ -105,6 +108,7 @@ def update_recipe(recipe_id: int, recipe_in: RecipeUpdate):
|
|||||||
tags=row["tags"] or [],
|
tags=row["tags"] or [],
|
||||||
ingredients=row["ingredients"] or [],
|
ingredients=row["ingredients"] or [],
|
||||||
steps=row["steps"] or [],
|
steps=row["steps"] or [],
|
||||||
|
image=row.get("image"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -136,6 +140,7 @@ def random_recipe(
|
|||||||
tags=r["tags"] or [],
|
tags=r["tags"] or [],
|
||||||
ingredients=r["ingredients"] or [],
|
ingredients=r["ingredients"] or [],
|
||||||
steps=r["steps"] or [],
|
steps=r["steps"] or [],
|
||||||
|
image=r.get("image"),
|
||||||
)
|
)
|
||||||
for r in rows
|
for r in rows
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,7 +7,8 @@ CREATE TABLE IF NOT EXISTS recipes (
|
|||||||
|
|
||||||
tags JSONB NOT NULL DEFAULT '[]', -- ["מהיר", "בריא"]
|
tags JSONB NOT NULL DEFAULT '[]', -- ["מהיר", "בריא"]
|
||||||
ingredients JSONB NOT NULL DEFAULT '[]', -- ["ביצה", "עגבניה", "מלח"]
|
ingredients JSONB NOT NULL DEFAULT '[]', -- ["ביצה", "עגבניה", "מלח"]
|
||||||
steps JSONB NOT NULL DEFAULT '[]' -- ["לחתוך", "לבשל", ...]
|
steps JSONB NOT NULL DEFAULT '[]', -- ["לחתוך", "לבשל", ...]
|
||||||
|
image TEXT -- Base64-encoded image or image URL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Optional: index for filters
|
-- Optional: index for filters
|
||||||
|
|||||||
25
frontend/src/assets/placeholder.svg
Normal file
25
frontend/src/assets/placeholder.svg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="200" height="200" fill="#e0e0e0"/>
|
||||||
|
|
||||||
|
<!-- Food plate icon -->
|
||||||
|
<circle cx="100" cy="100" r="70" fill="#c0c0c0" stroke="#999" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Fork -->
|
||||||
|
<g transform="translate(60, 80)">
|
||||||
|
<line x1="0" y1="0" x2="0" y2="35" stroke="#666" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="-8" y1="5" x2="-8" y2="35" stroke="#666" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="8" y1="5" x2="8" y2="35" stroke="#666" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="0" cy="0" r="3" fill="#666"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Knife -->
|
||||||
|
<g transform="translate(130, 80)">
|
||||||
|
<line x1="0" y1="0" x2="0" y2="35" stroke="#666" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<polygon points="0,0 -6,8 6,8" fill="#666"/>
|
||||||
|
<circle cx="0" cy="2" r="3" fill="#999"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- "No Image" text -->
|
||||||
|
<text x="100" y="155" font-family="Arial, sans-serif" font-size="14" fill="#666" text-anchor="middle" font-weight="bold">No Image</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,3 +1,5 @@
|
|||||||
|
import placeholderImage from "../assets/placeholder.svg";
|
||||||
|
|
||||||
function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal }) {
|
function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal }) {
|
||||||
if (!recipe) {
|
if (!recipe) {
|
||||||
return (
|
return (
|
||||||
@ -14,11 +16,9 @@ function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal }
|
|||||||
return (
|
return (
|
||||||
<section className="panel recipe-card">
|
<section className="panel recipe-card">
|
||||||
{/* Recipe Image */}
|
{/* Recipe Image */}
|
||||||
{recipe.image && (
|
|
||||||
<div className="recipe-image-container">
|
<div className="recipe-image-container">
|
||||||
<img src={recipe.image} alt={recipe.name} className="recipe-image" />
|
<img src={recipe.image || placeholderImage} alt={recipe.name} className="recipe-image" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<header className="recipe-header">
|
<header className="recipe-header">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import placeholderImage from "../assets/placeholder.svg";
|
||||||
|
|
||||||
function RecipeSearchList({
|
function RecipeSearchList({
|
||||||
allRecipes,
|
allRecipes,
|
||||||
@ -181,11 +182,9 @@ function RecipeSearchList({
|
|||||||
}
|
}
|
||||||
onClick={() => onSelect(r)}
|
onClick={() => onSelect(r)}
|
||||||
>
|
>
|
||||||
{r.image && (
|
|
||||||
<div className="recipe-list-image">
|
<div className="recipe-list-image">
|
||||||
<img src={r.image} alt={r.name} />
|
<img src={r.image || placeholderImage} alt={r.name} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div className="recipe-list-main">
|
<div className="recipe-list-main">
|
||||||
<div className="recipe-list-name">{r.name}</div>
|
<div className="recipe-list-name">{r.name}</div>
|
||||||
<div className="recipe-list-meta">
|
<div className="recipe-list-meta">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user