219 lines
7.0 KiB
JavaScript
219 lines
7.0 KiB
JavaScript
import { useState } from "react";
|
||
import placeholderImage from "../assets/placeholder.svg";
|
||
|
||
function RecipeSearchList({
|
||
allRecipes,
|
||
recipes,
|
||
selectedId,
|
||
onSelect,
|
||
searchQuery,
|
||
onSearchChange,
|
||
filterMealType,
|
||
onMealTypeChange,
|
||
filterMaxTime,
|
||
onMaxTimeChange,
|
||
filterTags,
|
||
onTagsChange,
|
||
}) {
|
||
const [expandFilters, setExpandFilters] = useState(false);
|
||
|
||
// Extract unique tags from ALL recipes (not filtered)
|
||
const allTags = Array.from(
|
||
new Set(allRecipes.flatMap((recipe) => recipe.tags || []))
|
||
).sort();
|
||
|
||
// Extract unique meal types from ALL recipes (not filtered)
|
||
const mealTypes = Array.from(new Set(allRecipes.map((r) => r.meal_type))).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;
|
||
|
||
const handleTagToggle = (tag) => {
|
||
if (filterTags.includes(tag)) {
|
||
onTagsChange(filterTags.filter((t) => t !== tag));
|
||
} else {
|
||
onTagsChange([...filterTags, tag]);
|
||
}
|
||
};
|
||
|
||
const clearAllFilters = () => {
|
||
onSearchChange("");
|
||
onMealTypeChange("");
|
||
onMaxTimeChange("");
|
||
onTagsChange([]);
|
||
};
|
||
|
||
const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0;
|
||
|
||
return (
|
||
<section className="panel secondary recipe-search-list">
|
||
{/* Header with Search */}
|
||
<div className="recipe-search-header">
|
||
<div className="search-box-wrapper">
|
||
<input
|
||
type="text"
|
||
className="search-box"
|
||
placeholder="חיפוש לפי שם מתכון..."
|
||
value={searchQuery}
|
||
onChange={(e) => onSearchChange(e.target.value)}
|
||
/>
|
||
{searchQuery && (
|
||
<button
|
||
className="search-clear"
|
||
onClick={() => onSearchChange("")}
|
||
title="נקה חיפוש"
|
||
>
|
||
✕
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{/* Filter Toggle Button */}
|
||
<button
|
||
className="filter-toggle-btn"
|
||
onClick={() => setExpandFilters(!expandFilters)}
|
||
title={expandFilters ? "הסתר סינונים" : "הצג סינונים"}
|
||
>
|
||
<span className="filter-icon">☰</span>
|
||
{hasActiveFilters && <span className="filter-badge">{Object.values([filterMealType, filterMaxTime, filterTags.length > 0 ? "tags" : null]).filter(Boolean).length}</span>}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Expandable Filters */}
|
||
{expandFilters && (
|
||
<div className="recipe-filters-expanded">
|
||
<div className="filters-grid">
|
||
{/* Meal Type Filter */}
|
||
{mealTypes.length > 0 && (
|
||
<div className="filter-group">
|
||
<label className="filter-label">סוג ארוחה</label>
|
||
<div className="filter-options">
|
||
<button
|
||
className={`filter-btn ${filterMealType === "" ? "active" : ""}`}
|
||
onClick={() => onMealTypeChange("")}
|
||
>
|
||
הכל
|
||
</button>
|
||
{mealTypes.map((type) => (
|
||
<button
|
||
key={type}
|
||
className={`filter-btn ${filterMealType === type ? "active" : ""}`}
|
||
onClick={() => onMealTypeChange(type)}
|
||
>
|
||
{translateMealType(type)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Prep Time Filter */}
|
||
{maxTimeInRecipes > 0 && (
|
||
<div className="filter-group">
|
||
<label className="filter-label">
|
||
זמן הכנה {filterMaxTime ? `עד ${filterMaxTime} דקות` : "הכל"}
|
||
</label>
|
||
<div className="time-filter-wrapper">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max={sliderMax}
|
||
step="1"
|
||
value={filterMaxTime || 0}
|
||
onChange={(e) => {
|
||
const val = parseInt(e.target.value, 10);
|
||
onMaxTimeChange(val === 0 ? "" : String(val));
|
||
}}
|
||
className="time-slider"
|
||
/>
|
||
<div className="time-labels">
|
||
<span>0</span>
|
||
<span>{sliderMax}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Tags Filter */}
|
||
{allTags.length > 0 && (
|
||
<div className="filter-group">
|
||
<label className="filter-label">תגיות</label>
|
||
<div className="tag-filter-wrapper">
|
||
{allTags.map((tag) => (
|
||
<button
|
||
key={tag}
|
||
className={`tag-filter-btn ${filterTags.includes(tag) ? "active" : ""}`}
|
||
onClick={() => handleTagToggle(tag)}
|
||
>
|
||
{tag}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Clear All Button */}
|
||
{hasActiveFilters && (
|
||
<button className="btn ghost full" onClick={clearAllFilters} style={{ marginTop: "0.6rem" }}>
|
||
נקה הכל
|
||
</button>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Recipe List Header */}
|
||
<div className="recipe-list-header">
|
||
<h3>כל המתכונים</h3>
|
||
<span className="badge">{recipes.length}</span>
|
||
</div>
|
||
|
||
{/* Recipe List */}
|
||
{recipes.length === 0 ? (
|
||
<p className="muted">עדיין אין מתכונים שתואמים את הסינונים שלך.</p>
|
||
) : (
|
||
<ul className="recipe-list">
|
||
{recipes.map((r) => (
|
||
<li
|
||
key={r.id}
|
||
className={
|
||
selectedId === r.id ? "recipe-list-item active" : "recipe-list-item"
|
||
}
|
||
onClick={() => onSelect(r)}
|
||
>
|
||
<div className="recipe-list-image">
|
||
<img src={r.image || placeholderImage} alt={r.name} />
|
||
</div>
|
||
<div className="recipe-list-main">
|
||
<div className="recipe-list-name">{r.name}</div>
|
||
<div className="recipe-list-meta">
|
||
<span>{r.time_minutes} דק׳</span>
|
||
<span>{translateMealType(r.meal_type)}</span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</section>
|
||
);
|
||
}
|
||
|
||
function translateMealType(type) {
|
||
switch (type) {
|
||
case "breakfast":
|
||
return "בוקר";
|
||
case "lunch":
|
||
return "צהריים";
|
||
case "dinner":
|
||
return "ערב";
|
||
case "snack":
|
||
return "נשנוש";
|
||
default:
|
||
return type;
|
||
}
|
||
}
|
||
|
||
export default RecipeSearchList;
|