297 lines
9.4 KiB
JavaScript
297 lines
9.4 KiB
JavaScript
import { useEffect, useState, useRef } from "react";
|
||
|
||
function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, currentUser = 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("");
|
||
|
||
const [ingredients, setIngredients] = useState([""]);
|
||
const [steps, setSteps] = useState([""]);
|
||
|
||
const lastIngredientRef = useRef(null);
|
||
const lastStepRef = useRef(null);
|
||
|
||
const isEditMode = !!editingRecipe;
|
||
|
||
useEffect(() => {
|
||
if (open) {
|
||
if (isEditMode) {
|
||
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 || [""]);
|
||
setSteps(editingRecipe.steps || [""]);
|
||
} else {
|
||
setName("");
|
||
setMealType("lunch");
|
||
setTimeMinutes(15);
|
||
setMadeBy(currentUser?.username || "");
|
||
setTags("");
|
||
setImage("");
|
||
setIngredients([""]);
|
||
setSteps([""]);
|
||
}
|
||
}
|
||
}, [open, editingRecipe, isEditMode, currentUser]);
|
||
|
||
if (!open) return null;
|
||
|
||
const handleAddIngredient = () => {
|
||
setIngredients((prev) => [...prev, ""]);
|
||
setTimeout(() => {
|
||
lastIngredientRef.current?.focus();
|
||
lastIngredientRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
}, 0);
|
||
};
|
||
|
||
const handleChangeIngredient = (idx, value) => {
|
||
setIngredients((prev) => prev.map((v, i) => (i === idx ? value : v)));
|
||
};
|
||
|
||
const handleRemoveIngredient = (idx) => {
|
||
setIngredients((prev) => prev.filter((_, i) => i !== idx || prev.length === 1));
|
||
};
|
||
|
||
const handleAddStep = () => {
|
||
setSteps((prev) => [...prev, ""]);
|
||
setTimeout(() => {
|
||
lastStepRef.current?.focus();
|
||
lastStepRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
}, 0);
|
||
};
|
||
|
||
const handleChangeStep = (idx, value) => {
|
||
setSteps((prev) => prev.map((v, i) => (i === idx ? value : v)));
|
||
};
|
||
|
||
const handleRemoveStep = (idx) => {
|
||
setSteps((prev) => prev.filter((_, i) => i !== idx || prev.length === 1));
|
||
};
|
||
|
||
const handleImageChange = (e) => {
|
||
const file = e.target.files?.[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onloadend = () => {
|
||
setImage(reader.result);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
};
|
||
|
||
const handleRemoveImage = () => {
|
||
setImage("");
|
||
};
|
||
|
||
const handleSubmit = (e) => {
|
||
e.preventDefault();
|
||
|
||
const cleanIngredients = ingredients.map((s) => s.trim()).filter(Boolean);
|
||
const cleanSteps = steps.map((s) => s.trim()).filter(Boolean);
|
||
const tagsArr = tags
|
||
.split(" ")
|
||
.map((t) => t.trim())
|
||
.filter(Boolean);
|
||
|
||
const payload = {
|
||
name,
|
||
meal_type: mealType,
|
||
time_minutes: Number(timeMinutes),
|
||
tags: tagsArr,
|
||
ingredients: cleanIngredients,
|
||
steps: cleanSteps,
|
||
made_by: madeBy.trim() || currentUser?.username || "",
|
||
};
|
||
|
||
if (image) {
|
||
payload.image = image;
|
||
}
|
||
|
||
onSubmit(payload);
|
||
};
|
||
|
||
return (
|
||
<div className="drawer-backdrop" onClick={onClose}>
|
||
<div className="drawer" onClick={(e) => e.stopPropagation()}>
|
||
<header className="drawer-header">
|
||
<h2>{isEditMode ? "ערוך מתכון" : "מתכון חדש"}</h2>
|
||
<button className="icon-btn" onClick={onClose}>
|
||
✕
|
||
</button>
|
||
</header>
|
||
|
||
<form className="drawer-body" onSubmit={handleSubmit}>
|
||
<div className="field">
|
||
<label>שם המתכון</label>
|
||
<input
|
||
value={name}
|
||
onChange={(e) => setName(e.target.value)}
|
||
required
|
||
placeholder="שקשוקה חריפה, פסטה שמנת, חזה עוף בתנור..."
|
||
/>
|
||
</div>
|
||
|
||
<div className="two-cols">
|
||
<div className="field">
|
||
<label>סוג ארוחה</label>
|
||
<select value={mealType} onChange={(e) => setMealType(e.target.value)}>
|
||
<option value="breakfast">בוקר</option>
|
||
<option value="lunch">צהריים</option>
|
||
<option value="dinner">ערב</option>
|
||
<option value="snack">קינוחים</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label>זמן הכנה (דקות)</label>
|
||
<input
|
||
type="number"
|
||
min="1"
|
||
value={timeMinutes}
|
||
onChange={(e) => setTimeMinutes(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label>המתכון של:</label>
|
||
<input
|
||
value={madeBy}
|
||
onChange={(e) => setMadeBy(e.target.value)}
|
||
placeholder="שם האדם שיצר את הגרסה הזו..."
|
||
/>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label>תמונה של המתכון</label>
|
||
<div className="image-upload-wrapper">
|
||
{image && (
|
||
<div className="image-preview">
|
||
<img src={image} alt="preview" />
|
||
<button
|
||
type="button"
|
||
className="image-remove-btn"
|
||
onClick={handleRemoveImage}
|
||
title="הסרת תמונה"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
)}
|
||
<input
|
||
type="file"
|
||
accept="image/*"
|
||
onChange={handleImageChange}
|
||
className="image-input"
|
||
id="recipe-image-input"
|
||
/>
|
||
<label htmlFor="recipe-image-input" className="image-upload-label">
|
||
{image ? "החלף תמונה" : "בחר תמונה"}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label>תגיות (מופרד ברווחים)</label>
|
||
<input
|
||
value={tags}
|
||
onChange={(e) => setTags(e.target.value)}
|
||
placeholder="מהיר טבעוני משפחתי..."
|
||
/>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label>מצרכים</label>
|
||
<div className="dynamic-list">
|
||
{ingredients.map((val, idx) => (
|
||
<div key={idx} className="dynamic-row">
|
||
<input
|
||
ref={idx === ingredients.length - 1 ? lastIngredientRef : null}
|
||
value={val}
|
||
onChange={(e) => handleChangeIngredient(idx, e.target.value)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
handleAddIngredient();
|
||
}
|
||
}}
|
||
placeholder="למשל: 2 ביצים"
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="icon-btn small"
|
||
onClick={() => handleRemoveIngredient(idx)}
|
||
>
|
||
−
|
||
</button>
|
||
</div>
|
||
))}
|
||
<button
|
||
type="button"
|
||
className="btn ghost small"
|
||
onClick={handleAddIngredient}
|
||
>
|
||
+ הוספת מצרך
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label>שלבים</label>
|
||
<div className="dynamic-list">
|
||
{steps.map((val, idx) => (
|
||
<div key={idx} className="dynamic-row">
|
||
<input
|
||
ref={idx === steps.length - 1 ? lastStepRef : null}
|
||
value={val}
|
||
onChange={(e) => handleChangeStep(idx, e.target.value)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
handleAddStep();
|
||
}
|
||
}}
|
||
placeholder="למשל: לחמם את התנור ל־180 מעלות"
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="icon-btn small"
|
||
onClick={() => handleRemoveStep(idx)}
|
||
>
|
||
−
|
||
</button>
|
||
</div>
|
||
))}
|
||
<button
|
||
type="button"
|
||
className="btn ghost small"
|
||
onClick={handleAddStep}
|
||
>
|
||
+ הוספת שלב
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<footer className="drawer-footer">
|
||
<button type="button" className="btn ghost" onClick={onClose}>
|
||
ביטול
|
||
</button>
|
||
<button type="submit" className="btn primary">
|
||
{isEditMode ? "עדכן מתכון" : "שמירת מתכון"}
|
||
</button>
|
||
</footer>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default RecipeFormDrawer;
|