Add made by
This commit is contained in:
parent
56d00c2ed8
commit
e0b0ec94fe
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, image
|
tags, ingredients, steps, image, made_by
|
||||||
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, image
|
recipe_data: name, meal_type, time_minutes, tags, ingredients, steps, image, made_by
|
||||||
"""
|
"""
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
try:
|
try:
|
||||||
@ -82,9 +82,10 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di
|
|||||||
tags = %s,
|
tags = %s,
|
||||||
ingredients = %s,
|
ingredients = %s,
|
||||||
steps = %s,
|
steps = %s,
|
||||||
image = %s
|
image = %s,
|
||||||
|
made_by = %s
|
||||||
WHERE id = %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"],
|
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("ingredients", [])),
|
||||||
json.dumps(recipe_data.get("steps", [])),
|
json.dumps(recipe_data.get("steps", [])),
|
||||||
recipe_data.get("image"),
|
recipe_data.get("image"),
|
||||||
|
recipe_data.get("made_by"),
|
||||||
recipe_id,
|
recipe_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -131,9 +133,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, image)
|
INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps, image, made_by)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %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"],
|
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("ingredients", [])),
|
||||||
json.dumps(recipe_data.get("steps", [])),
|
json.dumps(recipe_data.get("steps", [])),
|
||||||
recipe_data.get("image"),
|
recipe_data.get("image"),
|
||||||
|
recipe_data.get("made_by"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
@ -160,7 +163,7 @@ def get_recipes_by_filters_db(
|
|||||||
try:
|
try:
|
||||||
query = """
|
query = """
|
||||||
SELECT id, name, meal_type, time_minutes,
|
SELECT id, name, meal_type, time_minutes,
|
||||||
tags, ingredients, steps
|
tags, ingredients, steps, image, made_by
|
||||||
FROM recipes
|
FROM recipes
|
||||||
WHERE 1=1
|
WHERE 1=1
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class RecipeBase(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
meal_type: str # breakfast / lunch / dinner / snack
|
meal_type: str # breakfast / lunch / dinner / snack
|
||||||
time_minutes: int
|
time_minutes: int
|
||||||
|
made_by: Optional[str] = None # Person who created this recipe version
|
||||||
tags: List[str] = []
|
tags: List[str] = []
|
||||||
ingredients: List[str] = []
|
ingredients: List[str] = []
|
||||||
steps: List[str] = []
|
steps: List[str] = []
|
||||||
@ -63,6 +64,7 @@ def list_recipes():
|
|||||||
name=r["name"],
|
name=r["name"],
|
||||||
meal_type=r["meal_type"],
|
meal_type=r["meal_type"],
|
||||||
time_minutes=r["time_minutes"],
|
time_minutes=r["time_minutes"],
|
||||||
|
made_by=r.get("made_by"),
|
||||||
tags=r["tags"] or [],
|
tags=r["tags"] or [],
|
||||||
ingredients=r["ingredients"] or [],
|
ingredients=r["ingredients"] or [],
|
||||||
steps=r["steps"] or [],
|
steps=r["steps"] or [],
|
||||||
@ -85,6 +87,7 @@ def create_recipe(recipe_in: RecipeCreate):
|
|||||||
name=row["name"],
|
name=row["name"],
|
||||||
meal_type=row["meal_type"],
|
meal_type=row["meal_type"],
|
||||||
time_minutes=row["time_minutes"],
|
time_minutes=row["time_minutes"],
|
||||||
|
made_by=row.get("made_by"),
|
||||||
tags=row["tags"] or [],
|
tags=row["tags"] or [],
|
||||||
ingredients=row["ingredients"] or [],
|
ingredients=row["ingredients"] or [],
|
||||||
steps=row["steps"] or [],
|
steps=row["steps"] or [],
|
||||||
@ -105,6 +108,7 @@ def update_recipe(recipe_id: int, recipe_in: RecipeUpdate):
|
|||||||
name=row["name"],
|
name=row["name"],
|
||||||
meal_type=row["meal_type"],
|
meal_type=row["meal_type"],
|
||||||
time_minutes=row["time_minutes"],
|
time_minutes=row["time_minutes"],
|
||||||
|
made_by=row.get("made_by"),
|
||||||
tags=row["tags"] or [],
|
tags=row["tags"] or [],
|
||||||
ingredients=row["ingredients"] or [],
|
ingredients=row["ingredients"] or [],
|
||||||
steps=row["steps"] or [],
|
steps=row["steps"] or [],
|
||||||
@ -137,6 +141,7 @@ def random_recipe(
|
|||||||
name=r["name"],
|
name=r["name"],
|
||||||
meal_type=r["meal_type"],
|
meal_type=r["meal_type"],
|
||||||
time_minutes=r["time_minutes"],
|
time_minutes=r["time_minutes"],
|
||||||
|
made_by=r.get("made_by"),
|
||||||
tags=r["tags"] or [],
|
tags=r["tags"] or [],
|
||||||
ingredients=r["ingredients"] or [],
|
ingredients=r["ingredients"] or [],
|
||||||
steps=r["steps"] or [],
|
steps=r["steps"] or [],
|
||||||
|
|||||||
@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS recipes (
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack
|
meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack
|
||||||
time_minutes INTEGER NOT NULL,
|
time_minutes INTEGER NOT NULL,
|
||||||
|
made_by TEXT, -- Person who created this recipe version
|
||||||
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 '[]', -- ["לחתוך", "לבשל", ...]
|
||||||
@ -18,8 +18,12 @@ CREATE INDEX IF NOT EXISTS idx_recipes_meal_type
|
|||||||
CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes
|
CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes
|
||||||
ON 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
|
CREATE INDEX IF NOT EXISTS idx_recipes_tags_jsonb
|
||||||
ON recipes USING GIN (tags);
|
ON recipes USING GIN (tags);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb
|
CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb
|
||||||
ON recipes USING GIN (ingredients);
|
ON recipes USING GIN (ingredients);
|
||||||
|
|
||||||
|
|||||||
@ -314,6 +314,13 @@ select {
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recipe-made-by {
|
||||||
|
margin: 0.3rem 0 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.pill-row {
|
.pill-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ function App() {
|
|||||||
const [filterMealType, setFilterMealType] = useState("");
|
const [filterMealType, setFilterMealType] = useState("");
|
||||||
const [filterMaxTime, setFilterMaxTime] = useState("");
|
const [filterMaxTime, setFilterMaxTime] = useState("");
|
||||||
const [filterTags, setFilterTags] = useState([]);
|
const [filterTags, setFilterTags] = useState([]);
|
||||||
|
const [filterMadeBy, setFilterMadeBy] = useState("");
|
||||||
|
|
||||||
// Random recipe filters
|
// Random recipe filters
|
||||||
const [mealTypeFilter, setMealTypeFilter] = useState("");
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -222,6 +228,8 @@ function App() {
|
|||||||
onMaxTimeChange={setFilterMaxTime}
|
onMaxTimeChange={setFilterMaxTime}
|
||||||
filterTags={filterTags}
|
filterTags={filterTags}
|
||||||
onTagsChange={setFilterTags}
|
onTagsChange={setFilterTags}
|
||||||
|
filterMadeBy={filterMadeBy}
|
||||||
|
onMadeByChange={setFilterMadeBy}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,9 @@ function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal }
|
|||||||
<p className="recipe-subtitle">
|
<p className="recipe-subtitle">
|
||||||
{translateMealType(recipe.meal_type)} · {recipe.time_minutes} דקות הכנה
|
{translateMealType(recipe.meal_type)} · {recipe.time_minutes} דקות הכנה
|
||||||
</p>
|
</p>
|
||||||
|
{recipe.made_by && (
|
||||||
|
<h4 className="recipe-made-by">המתכון של: {recipe.made_by}</h4>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="pill-row">
|
<div className="pill-row">
|
||||||
<span className="pill">⏱ {recipe.time_minutes} דק׳</span>
|
<span className="pill">⏱ {recipe.time_minutes} דק׳</span>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
|
|||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [mealType, setMealType] = useState("lunch");
|
const [mealType, setMealType] = useState("lunch");
|
||||||
const [timeMinutes, setTimeMinutes] = useState(15);
|
const [timeMinutes, setTimeMinutes] = useState(15);
|
||||||
|
const [madeBy, setMadeBy] = useState("");
|
||||||
const [tags, setTags] = useState("");
|
const [tags, setTags] = useState("");
|
||||||
const [image, setImage] = useState("");
|
const [image, setImage] = useState("");
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
|
|||||||
setName(editingRecipe.name || "");
|
setName(editingRecipe.name || "");
|
||||||
setMealType(editingRecipe.meal_type || "lunch");
|
setMealType(editingRecipe.meal_type || "lunch");
|
||||||
setTimeMinutes(editingRecipe.time_minutes || 15);
|
setTimeMinutes(editingRecipe.time_minutes || 15);
|
||||||
|
setMadeBy(editingRecipe.made_by || "");
|
||||||
setTags((editingRecipe.tags || []).join(", "));
|
setTags((editingRecipe.tags || []).join(", "));
|
||||||
setImage(editingRecipe.image || "");
|
setImage(editingRecipe.image || "");
|
||||||
setIngredients(editingRecipe.ingredients || [""]);
|
setIngredients(editingRecipe.ingredients || [""]);
|
||||||
@ -26,6 +28,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
|
|||||||
setName("");
|
setName("");
|
||||||
setMealType("lunch");
|
setMealType("lunch");
|
||||||
setTimeMinutes(15);
|
setTimeMinutes(15);
|
||||||
|
setMadeBy("");
|
||||||
setTags("");
|
setTags("");
|
||||||
setImage("");
|
setImage("");
|
||||||
setIngredients([""]);
|
setIngredients([""]);
|
||||||
@ -94,6 +97,10 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
|
|||||||
steps: cleanSteps,
|
steps: cleanSteps,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (madeBy.trim()) {
|
||||||
|
payload.made_by = madeBy.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
payload.image = image;
|
payload.image = image;
|
||||||
}
|
}
|
||||||
@ -145,6 +152,15 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="field">
|
||||||
|
<label>המתכון של:</label>
|
||||||
|
<input
|
||||||
|
value={madeBy}
|
||||||
|
onChange={(e) => setMadeBy(e.target.value)}
|
||||||
|
placeholder="שם האדם שיצר את הגרסה הזו..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label>תמונה של המתכון</label>
|
<label>תמונה של המתכון</label>
|
||||||
<div className="image-upload-wrapper">
|
<div className="image-upload-wrapper">
|
||||||
|
|||||||
@ -14,6 +14,8 @@ function RecipeSearchList({
|
|||||||
onMaxTimeChange,
|
onMaxTimeChange,
|
||||||
filterTags,
|
filterTags,
|
||||||
onTagsChange,
|
onTagsChange,
|
||||||
|
filterMadeBy,
|
||||||
|
onMadeByChange,
|
||||||
}) {
|
}) {
|
||||||
const [expandFilters, setExpandFilters] = useState(false);
|
const [expandFilters, setExpandFilters] = useState(false);
|
||||||
|
|
||||||
@ -25,6 +27,9 @@ function RecipeSearchList({
|
|||||||
// Extract unique meal types from ALL recipes (not filtered)
|
// Extract unique meal types from ALL recipes (not filtered)
|
||||||
const mealTypes = Array.from(new Set(allRecipes.map((r) => r.meal_type))).sort();
|
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
|
// 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 maxTimeInRecipes = Math.max(...allRecipes.map((r) => r.time_minutes), 0);
|
||||||
const sliderMax = maxTimeInRecipes > 0 ? maxTimeInRecipes + 10 : 70;
|
const sliderMax = maxTimeInRecipes > 0 ? maxTimeInRecipes + 10 : 70;
|
||||||
@ -42,9 +47,10 @@ function RecipeSearchList({
|
|||||||
onMealTypeChange("");
|
onMealTypeChange("");
|
||||||
onMaxTimeChange("");
|
onMaxTimeChange("");
|
||||||
onTagsChange([]);
|
onTagsChange([]);
|
||||||
|
onMadeByChange("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0;
|
const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterMadeBy;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel secondary recipe-search-list">
|
<section className="panel secondary recipe-search-list">
|
||||||
@ -152,6 +158,30 @@ function RecipeSearchList({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Made By Filter */}
|
||||||
|
{allMadeBy.length > 0 && (
|
||||||
|
<div className="filter-group">
|
||||||
|
<label className="filter-label">המתכונים של:</label>
|
||||||
|
<div className="filter-options">
|
||||||
|
<button
|
||||||
|
className={`filter-btn ${filterMadeBy === "" ? "active" : ""}`}
|
||||||
|
onClick={() => onMadeByChange("")}
|
||||||
|
>
|
||||||
|
הכל
|
||||||
|
</button>
|
||||||
|
{allMadeBy.map((person) => (
|
||||||
|
<button
|
||||||
|
key={person}
|
||||||
|
className={`filter-btn ${filterMadeBy === person ? "active" : ""}`}
|
||||||
|
onClick={() => onMadeByChange(person)}
|
||||||
|
>
|
||||||
|
{person}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Clear All Button */}
|
{/* Clear All Button */}
|
||||||
|
|||||||
35
my-recipes-chart/templates/db-schema-configmap.yaml
Normal file
35
my-recipes-chart/templates/db-schema-configmap.yaml
Normal file
@ -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);
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user