Added search bar / filtering for recipes (#6019)

This commit is contained in:
Zane
2025-12-08 19:41:30 -08:00
committed by GitHub
parent f71d2199fc
commit 4438a16918
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { listSavedRecipes, convertToLocaleDateString } from '../../recipe/recipe_management'; import { listSavedRecipes, convertToLocaleDateString } from '../../recipe/recipe_management';
import { import {
FileText, FileText,
@@ -32,6 +32,7 @@ import { generateDeepLink, Recipe } from '../../recipe';
import { useNavigation } from '../../hooks/useNavigation'; import { useNavigation } from '../../hooks/useNavigation';
import { CronPicker } from '../schedule/CronPicker'; import { CronPicker } from '../schedule/CronPicker';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog';
import { SearchView } from '../conversation/SearchView';
import cronstrue from 'cronstrue'; import cronstrue from 'cronstrue';
export default function RecipesView() { export default function RecipesView() {
@@ -57,6 +58,26 @@ export default function RecipesView() {
const [slashCommand, setSlashCommand] = useState<string>(''); const [slashCommand, setSlashCommand] = useState<string>('');
const [scheduleValid, setScheduleIsValid] = useState(true); const [scheduleValid, setScheduleIsValid] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const filteredRecipes = useMemo(() => {
if (!searchTerm) return savedRecipes;
const searchLower = searchTerm.toLowerCase();
return savedRecipes.filter((recipeManifest) => {
const { recipe, slash_command } = recipeManifest;
const title = recipe.title?.toLowerCase() || '';
const description = recipe.description?.toLowerCase() || '';
const slashCmd = slash_command?.toLowerCase() || '';
return (
title.includes(searchLower) ||
description.includes(searchLower) ||
slashCmd.includes(searchLower)
);
});
}, [savedRecipes, searchTerm]);
useEffect(() => { useEffect(() => {
loadSavedRecipes(); loadSavedRecipes();
}, []); }, []);
@@ -485,9 +506,19 @@ export default function RecipesView() {
); );
} }
if (filteredRecipes.length === 0 && searchTerm) {
return (
<div className="flex flex-col items-center justify-center h-full text-text-muted mt-4">
<FileText className="h-12 w-12 mb-4" />
<p className="text-lg mb-2">No matching recipes found</p>
<p className="text-sm">Try adjusting your search terms</p>
</div>
);
}
return ( return (
<div className="space-y-2"> <div className="space-y-2">
{savedRecipes.map((recipeManifestResponse: RecipeManifest) => ( {filteredRecipes.map((recipeManifestResponse: RecipeManifest) => (
<RecipeItem <RecipeItem
key={recipeManifestResponse.id} key={recipeManifestResponse.id}
recipeManifestResponse={recipeManifestResponse} recipeManifestResponse={recipeManifestResponse}
@@ -520,20 +551,22 @@ export default function RecipesView() {
</div> </div>
<p className="text-sm text-text-muted mb-1"> <p className="text-sm text-text-muted mb-1">
View and manage your saved recipes to quickly start new sessions with predefined View and manage your saved recipes to quickly start new sessions with predefined
configurations. configurations. F/Ctrl+F to search.
</p> </p>
</div> </div>
</div> </div>
<div className="flex-1 min-h-0 relative px-8"> <div className="flex-1 min-h-0 relative px-8">
<ScrollArea className="h-full"> <ScrollArea className="h-full">
<div <SearchView onSearch={(term) => setSearchTerm(term)} placeholder="Search recipes...">
className={`h-full relative transition-all duration-300 ${ <div
showContent ? 'opacity-100 animate-in fade-in ' : 'opacity-0' className={`h-full relative transition-all duration-300 ${
}`} showContent ? 'opacity-100 animate-in fade-in ' : 'opacity-0'
> }`}
{renderContent()} >
</div> {renderContent()}
</div>
</SearchView>
</ScrollArea> </ScrollArea>
</div> </div>
</div> </div>