my-recipes/frontend/src/components/RecipeSearchList.jsx

219 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;