refactor: remove category feature from UI, focus on tags
All checks were successful
Basil CI/CD Pipeline / Shared Package Tests (push) Successful in 1m22s
Basil CI/CD Pipeline / Code Linting (push) Successful in 1m30s
Basil CI/CD Pipeline / Web Tests (push) Successful in 1m37s
Basil CI/CD Pipeline / API Tests (push) Successful in 1m50s
Basil CI/CD Pipeline / Security Scanning (push) Successful in 1m16s
Basil CI/CD Pipeline / Build All Packages (push) Successful in 1m32s
Basil CI/CD Pipeline / E2E Tests (push) Has been skipped
Basil CI/CD Pipeline / Build & Push Docker Images (push) Successful in 5m6s
Basil CI/CD Pipeline / Trigger Deployment (push) Successful in 12s
All checks were successful
Basil CI/CD Pipeline / Shared Package Tests (push) Successful in 1m22s
Basil CI/CD Pipeline / Code Linting (push) Successful in 1m30s
Basil CI/CD Pipeline / Web Tests (push) Successful in 1m37s
Basil CI/CD Pipeline / API Tests (push) Successful in 1m50s
Basil CI/CD Pipeline / Security Scanning (push) Successful in 1m16s
Basil CI/CD Pipeline / Build All Packages (push) Successful in 1m32s
Basil CI/CD Pipeline / E2E Tests (push) Has been skipped
Basil CI/CD Pipeline / Build & Push Docker Images (push) Successful in 5m6s
Basil CI/CD Pipeline / Trigger Deployment (push) Successful in 12s
Removed all category-related UI elements from the web application to simplify recipe organization. Users will now use tags exclusively for categorizing recipes and cookbooks. Changes: - Remove category input fields from RecipeForm and UnifiedEditRecipe - Remove category filters from CookbookDetail - Remove category auto-add feature from Cookbooks and EditCookbook - Preserve category data in database for backward compatibility - Keep API category support for future use or migrations This change reduces user confusion by having a single organizational method (tags) instead of overlapping categories and tags. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,6 @@ function CookbookDetail() {
|
|||||||
// Filters
|
// Filters
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string>('');
|
|
||||||
const [selectedCuisine, setSelectedCuisine] = useState<string>('');
|
const [selectedCuisine, setSelectedCuisine] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,7 +70,7 @@ function CookbookDetail() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all unique tags and categories from recipes
|
// Get all unique tags from recipes
|
||||||
const getAllTags = (): string[] => {
|
const getAllTags = (): string[] => {
|
||||||
if (!cookbook) return [];
|
if (!cookbook) return [];
|
||||||
const tagSet = new Set<string>();
|
const tagSet = new Set<string>();
|
||||||
@@ -81,17 +80,6 @@ function CookbookDetail() {
|
|||||||
return Array.from(tagSet).sort();
|
return Array.from(tagSet).sort();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAllCategories = (): string[] => {
|
|
||||||
if (!cookbook) return [];
|
|
||||||
const categorySet = new Set<string>();
|
|
||||||
cookbook.recipes.forEach(recipe => {
|
|
||||||
if (recipe.categories) {
|
|
||||||
recipe.categories.forEach(cat => categorySet.add(cat));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Array.from(categorySet).sort();
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllCuisines = (): string[] => {
|
const getAllCuisines = (): string[] => {
|
||||||
if (!cookbook) return [];
|
if (!cookbook) return [];
|
||||||
const cuisineSet = new Set<string>();
|
const cuisineSet = new Set<string>();
|
||||||
@@ -121,14 +109,6 @@ function CookbookDetail() {
|
|||||||
if (!hasAllTags) return false;
|
if (!hasAllTags) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category filter
|
|
||||||
if (selectedCategory) {
|
|
||||||
const recipeCategories = recipe.categories || [];
|
|
||||||
if (!recipeCategories.includes(selectedCategory)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cuisine filter
|
// Cuisine filter
|
||||||
if (selectedCuisine && recipe.cuisine !== selectedCuisine) {
|
if (selectedCuisine && recipe.cuisine !== selectedCuisine) {
|
||||||
return false;
|
return false;
|
||||||
@@ -141,7 +121,6 @@ function CookbookDetail() {
|
|||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
setSelectedTags([]);
|
setSelectedTags([]);
|
||||||
setSelectedCategory('');
|
|
||||||
setSelectedCuisine('');
|
setSelectedCuisine('');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -164,9 +143,8 @@ function CookbookDetail() {
|
|||||||
|
|
||||||
const filteredRecipes = getFilteredRecipes();
|
const filteredRecipes = getFilteredRecipes();
|
||||||
const allTags = getAllTags();
|
const allTags = getAllTags();
|
||||||
const allCategories = getAllCategories();
|
|
||||||
const allCuisines = getAllCuisines();
|
const allCuisines = getAllCuisines();
|
||||||
const hasActiveFilters = searchQuery || selectedTags.length > 0 || selectedCategory || selectedCuisine;
|
const hasActiveFilters = searchQuery || selectedTags.length > 0 || selectedCuisine;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cookbook-detail-page">
|
<div className="cookbook-detail-page">
|
||||||
@@ -220,22 +198,6 @@ function CookbookDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="filter-row">
|
<div className="filter-row">
|
||||||
{allCategories.length > 0 && (
|
|
||||||
<div className="filter-group">
|
|
||||||
<label htmlFor="category-filter">Category:</label>
|
|
||||||
<select
|
|
||||||
id="category-filter"
|
|
||||||
value={selectedCategory}
|
|
||||||
onChange={(e) => setSelectedCategory(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">All Categories</option>
|
|
||||||
{allCategories.map(category => (
|
|
||||||
<option key={category} value={category}>{category}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{allCuisines.length > 0 && (
|
{allCuisines.length > 0 && (
|
||||||
<div className="filter-group">
|
<div className="filter-group">
|
||||||
<label htmlFor="cuisine-filter">Cuisine:</label>
|
<label htmlFor="cuisine-filter">Cuisine:</label>
|
||||||
|
|||||||
@@ -13,16 +13,13 @@ function Cookbooks() {
|
|||||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||||
const [newCookbookName, setNewCookbookName] = useState('');
|
const [newCookbookName, setNewCookbookName] = useState('');
|
||||||
const [newCookbookDescription, setNewCookbookDescription] = useState('');
|
const [newCookbookDescription, setNewCookbookDescription] = useState('');
|
||||||
const [autoFilterCategories, setAutoFilterCategories] = useState<string[]>([]);
|
|
||||||
const [autoFilterTags, setAutoFilterTags] = useState<string[]>([]);
|
const [autoFilterTags, setAutoFilterTags] = useState<string[]>([]);
|
||||||
const [autoFilterCookbookTags, setAutoFilterCookbookTags] = useState<string[]>([]);
|
const [autoFilterCookbookTags, setAutoFilterCookbookTags] = useState<string[]>([]);
|
||||||
const [cookbookTags, setCookbookTags] = useState<string[]>([]);
|
const [cookbookTags, setCookbookTags] = useState<string[]>([]);
|
||||||
const [categoryInput, setCategoryInput] = useState('');
|
|
||||||
const [tagInput, setTagInput] = useState('');
|
const [tagInput, setTagInput] = useState('');
|
||||||
const [cookbookTagInput, setCookbookTagInput] = useState('');
|
const [cookbookTagInput, setCookbookTagInput] = useState('');
|
||||||
const [cookbookTagFilterInput, setCookbookTagFilterInput] = useState('');
|
const [cookbookTagFilterInput, setCookbookTagFilterInput] = useState('');
|
||||||
const [availableTags, setAvailableTags] = useState<Tag[]>([]);
|
const [availableTags, setAvailableTags] = useState<Tag[]>([]);
|
||||||
const [availableCategories, setAvailableCategories] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
loadData();
|
||||||
@@ -41,15 +38,6 @@ function Cookbooks() {
|
|||||||
setRecentRecipes(recipesResponse.data || []);
|
setRecentRecipes(recipesResponse.data || []);
|
||||||
setAvailableTags(tagsResponse.data || []);
|
setAvailableTags(tagsResponse.data || []);
|
||||||
|
|
||||||
// Extract unique categories from recent recipes
|
|
||||||
const categories = new Set<string>();
|
|
||||||
(recipesResponse.data || []).forEach(recipe => {
|
|
||||||
if (recipe.categories) {
|
|
||||||
recipe.categories.forEach(cat => categories.add(cat));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setAvailableCategories(Array.from(categories).sort());
|
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to load data');
|
setError('Failed to load data');
|
||||||
@@ -71,7 +59,6 @@ function Cookbooks() {
|
|||||||
await cookbooksApi.create({
|
await cookbooksApi.create({
|
||||||
name: newCookbookName,
|
name: newCookbookName,
|
||||||
description: newCookbookDescription || undefined,
|
description: newCookbookDescription || undefined,
|
||||||
autoFilterCategories: autoFilterCategories.length > 0 ? autoFilterCategories : undefined,
|
|
||||||
autoFilterTags: autoFilterTags.length > 0 ? autoFilterTags : undefined,
|
autoFilterTags: autoFilterTags.length > 0 ? autoFilterTags : undefined,
|
||||||
autoFilterCookbookTags: autoFilterCookbookTags.length > 0 ? autoFilterCookbookTags : undefined,
|
autoFilterCookbookTags: autoFilterCookbookTags.length > 0 ? autoFilterCookbookTags : undefined,
|
||||||
tags: cookbookTags.length > 0 ? cookbookTags : undefined
|
tags: cookbookTags.length > 0 ? cookbookTags : undefined
|
||||||
@@ -79,11 +66,9 @@ function Cookbooks() {
|
|||||||
|
|
||||||
setNewCookbookName('');
|
setNewCookbookName('');
|
||||||
setNewCookbookDescription('');
|
setNewCookbookDescription('');
|
||||||
setAutoFilterCategories([]);
|
|
||||||
setAutoFilterTags([]);
|
setAutoFilterTags([]);
|
||||||
setAutoFilterCookbookTags([]);
|
setAutoFilterCookbookTags([]);
|
||||||
setCookbookTags([]);
|
setCookbookTags([]);
|
||||||
setCategoryInput('');
|
|
||||||
setTagInput('');
|
setTagInput('');
|
||||||
setCookbookTagInput('');
|
setCookbookTagInput('');
|
||||||
setCookbookTagFilterInput('');
|
setCookbookTagFilterInput('');
|
||||||
@@ -95,18 +80,6 @@ function Cookbooks() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddCategory = () => {
|
|
||||||
const trimmed = categoryInput.trim();
|
|
||||||
if (trimmed && !autoFilterCategories.includes(trimmed)) {
|
|
||||||
setAutoFilterCategories([...autoFilterCategories, trimmed]);
|
|
||||||
setCategoryInput('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveCategory = (category: string) => {
|
|
||||||
setAutoFilterCategories(autoFilterCategories.filter(c => c !== category));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddTag = () => {
|
const handleAddTag = () => {
|
||||||
const trimmed = tagInput.trim();
|
const trimmed = tagInput.trim();
|
||||||
if (trimmed && !autoFilterTags.includes(trimmed)) {
|
if (trimmed && !autoFilterTags.includes(trimmed)) {
|
||||||
@@ -293,35 +266,6 @@ function Cookbooks() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Auto-Add Categories (Optional)</label>
|
|
||||||
<p className="help-text">Recipes with these categories will be automatically added to this cookbook</p>
|
|
||||||
<div className="filter-chips">
|
|
||||||
{autoFilterCategories.map(category => (
|
|
||||||
<span key={category} className="filter-chip">
|
|
||||||
{category}
|
|
||||||
<button type="button" onClick={() => handleRemoveCategory(category)}>×</button>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="input-with-button">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={categoryInput}
|
|
||||||
onChange={(e) => setCategoryInput(e.target.value)}
|
|
||||||
onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), handleAddCategory())}
|
|
||||||
placeholder="Add category"
|
|
||||||
list="available-categories"
|
|
||||||
/>
|
|
||||||
<button type="button" onClick={handleAddCategory} className="btn-add-filter">+</button>
|
|
||||||
</div>
|
|
||||||
<datalist id="available-categories">
|
|
||||||
{availableCategories.map(category => (
|
|
||||||
<option key={category} value={category} />
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Auto-Add Tags (Optional)</label>
|
<label>Auto-Add Tags (Optional)</label>
|
||||||
<p className="help-text">Recipes with these tags will be automatically added to this cookbook</p>
|
<p className="help-text">Recipes with these tags will be automatically added to this cookbook</p>
|
||||||
|
|||||||
@@ -18,17 +18,14 @@ function EditCookbook() {
|
|||||||
const [coverImageUrl, setCoverImageUrl] = useState('');
|
const [coverImageUrl, setCoverImageUrl] = useState('');
|
||||||
const [imageUrlInput, setImageUrlInput] = useState('');
|
const [imageUrlInput, setImageUrlInput] = useState('');
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [autoFilterCategories, setAutoFilterCategories] = useState<string[]>([]);
|
|
||||||
const [autoFilterTags, setAutoFilterTags] = useState<string[]>([]);
|
const [autoFilterTags, setAutoFilterTags] = useState<string[]>([]);
|
||||||
const [autoFilterCookbookTags, setAutoFilterCookbookTags] = useState<string[]>([]);
|
const [autoFilterCookbookTags, setAutoFilterCookbookTags] = useState<string[]>([]);
|
||||||
const [cookbookTags, setCookbookTags] = useState<string[]>([]);
|
const [cookbookTags, setCookbookTags] = useState<string[]>([]);
|
||||||
|
|
||||||
const [categoryInput, setCategoryInput] = useState('');
|
|
||||||
const [tagInput, setTagInput] = useState('');
|
const [tagInput, setTagInput] = useState('');
|
||||||
const [cookbookTagInput, setCookbookTagInput] = useState('');
|
const [cookbookTagInput, setCookbookTagInput] = useState('');
|
||||||
const [cookbookTagFilterInput, setCookbookTagFilterInput] = useState('');
|
const [cookbookTagFilterInput, setCookbookTagFilterInput] = useState('');
|
||||||
const [availableTags, setAvailableTags] = useState<Tag[]>([]);
|
const [availableTags, setAvailableTags] = useState<Tag[]>([]);
|
||||||
const [availableCategories, setAvailableCategories] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -49,7 +46,6 @@ function EditCookbook() {
|
|||||||
setName(cookbook.name);
|
setName(cookbook.name);
|
||||||
setDescription(cookbook.description || '');
|
setDescription(cookbook.description || '');
|
||||||
setCoverImageUrl(cookbook.coverImageUrl || '');
|
setCoverImageUrl(cookbook.coverImageUrl || '');
|
||||||
setAutoFilterCategories(cookbook.autoFilterCategories || []);
|
|
||||||
setAutoFilterTags(cookbook.autoFilterTags || []);
|
setAutoFilterTags(cookbook.autoFilterTags || []);
|
||||||
setAutoFilterCookbookTags(cookbook.autoFilterCookbookTags || []);
|
setAutoFilterCookbookTags(cookbook.autoFilterCookbookTags || []);
|
||||||
setCookbookTags(cookbook.tags || []);
|
setCookbookTags(cookbook.tags || []);
|
||||||
@@ -57,17 +53,6 @@ function EditCookbook() {
|
|||||||
|
|
||||||
setAvailableTags(tagsResponse.data || []);
|
setAvailableTags(tagsResponse.data || []);
|
||||||
|
|
||||||
// Extract unique categories from cookbook's recipes
|
|
||||||
const categories = new Set<string>();
|
|
||||||
if (cookbook && 'recipes' in cookbook) {
|
|
||||||
(cookbook as any).recipes.forEach((recipe: any) => {
|
|
||||||
if (recipe.categories) {
|
|
||||||
recipe.categories.forEach((cat: string) => categories.add(cat));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setAvailableCategories(Array.from(categories).sort());
|
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load cookbook:', err);
|
console.error('Failed to load cookbook:', err);
|
||||||
@@ -91,7 +76,6 @@ function EditCookbook() {
|
|||||||
name,
|
name,
|
||||||
description: description || undefined,
|
description: description || undefined,
|
||||||
coverImageUrl: coverImageUrl === '' ? '' : (coverImageUrl || undefined),
|
coverImageUrl: coverImageUrl === '' ? '' : (coverImageUrl || undefined),
|
||||||
autoFilterCategories,
|
|
||||||
autoFilterTags,
|
autoFilterTags,
|
||||||
autoFilterCookbookTags,
|
autoFilterCookbookTags,
|
||||||
tags: cookbookTags
|
tags: cookbookTags
|
||||||
@@ -106,18 +90,6 @@ function EditCookbook() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddCategory = () => {
|
|
||||||
const trimmed = categoryInput.trim();
|
|
||||||
if (trimmed && !autoFilterCategories.includes(trimmed)) {
|
|
||||||
setAutoFilterCategories([...autoFilterCategories, trimmed]);
|
|
||||||
setCategoryInput('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveCategory = (category: string) => {
|
|
||||||
setAutoFilterCategories(autoFilterCategories.filter(c => c !== category));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddTag = () => {
|
const handleAddTag = () => {
|
||||||
const trimmed = tagInput.trim();
|
const trimmed = tagInput.trim();
|
||||||
if (trimmed && !autoFilterTags.includes(trimmed)) {
|
if (trimmed && !autoFilterTags.includes(trimmed)) {
|
||||||
@@ -265,7 +237,6 @@ function EditCookbook() {
|
|||||||
name,
|
name,
|
||||||
description: description || undefined,
|
description: description || undefined,
|
||||||
coverImageUrl: '',
|
coverImageUrl: '',
|
||||||
autoFilterCategories,
|
|
||||||
autoFilterTags
|
autoFilterTags
|
||||||
});
|
});
|
||||||
setCoverImageUrl('');
|
setCoverImageUrl('');
|
||||||
@@ -339,39 +310,6 @@ function EditCookbook() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Auto-Add Categories</label>
|
|
||||||
<p className="help-text">
|
|
||||||
Recipes with these categories will be automatically added to this cookbook
|
|
||||||
</p>
|
|
||||||
<div className="filter-chips">
|
|
||||||
{autoFilterCategories.map(category => (
|
|
||||||
<span key={category} className="filter-chip">
|
|
||||||
{category}
|
|
||||||
<button type="button" onClick={() => handleRemoveCategory(category)}>×</button>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="input-with-button">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={categoryInput}
|
|
||||||
onChange={(e) => setCategoryInput(e.target.value)}
|
|
||||||
onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), handleAddCategory())}
|
|
||||||
placeholder="Add category"
|
|
||||||
list="available-categories"
|
|
||||||
/>
|
|
||||||
<button type="button" onClick={handleAddCategory} className="btn-add-filter">
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<datalist id="available-categories">
|
|
||||||
{availableCategories.map(category => (
|
|
||||||
<option key={category} value={category} />
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Auto-Add Tags</label>
|
<label>Auto-Add Tags</label>
|
||||||
<p className="help-text">
|
<p className="help-text">
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ function RecipeForm({ initialRecipe, onSubmit, onCancel }: RecipeFormProps) {
|
|||||||
const [cookTime, setCookTime] = useState(initialRecipe?.cookTime?.toString() || '');
|
const [cookTime, setCookTime] = useState(initialRecipe?.cookTime?.toString() || '');
|
||||||
const [servings, setServings] = useState(initialRecipe?.servings?.toString() || '');
|
const [servings, setServings] = useState(initialRecipe?.servings?.toString() || '');
|
||||||
const [cuisine, setCuisine] = useState(initialRecipe?.cuisine || '');
|
const [cuisine, setCuisine] = useState(initialRecipe?.cuisine || '');
|
||||||
const [categories, setCategories] = useState<string[]>(initialRecipe?.categories || []);
|
|
||||||
const [categoryInput, setCategoryInput] = useState('');
|
|
||||||
|
|
||||||
// Image handling
|
// Image handling
|
||||||
const [uploadingImage, setUploadingImage] = useState(false);
|
const [uploadingImage, setUploadingImage] = useState(false);
|
||||||
@@ -277,28 +275,6 @@ function RecipeForm({ initialRecipe, onSubmit, onCancel }: RecipeFormProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Category management
|
// Category management
|
||||||
const handleAddCategory = () => {
|
|
||||||
const trimmedCategory = categoryInput.trim();
|
|
||||||
if (!trimmedCategory) return;
|
|
||||||
if (categories.includes(trimmedCategory)) {
|
|
||||||
setCategoryInput('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCategories([...categories, trimmedCategory]);
|
|
||||||
setCategoryInput('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveCategory = (categoryToRemove: string) => {
|
|
||||||
setCategories(categories.filter(cat => cat !== categoryToRemove));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCategoryInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
handleAddCategory();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -309,7 +285,6 @@ function RecipeForm({ initialRecipe, onSubmit, onCancel }: RecipeFormProps) {
|
|||||||
cookTime: cookTime ? parseInt(cookTime) : undefined,
|
cookTime: cookTime ? parseInt(cookTime) : undefined,
|
||||||
servings: servings ? parseInt(servings) : undefined,
|
servings: servings ? parseInt(servings) : undefined,
|
||||||
cuisine: cuisine || undefined,
|
cuisine: cuisine || undefined,
|
||||||
categories: categories.length > 0 ? categories : undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (useSections) {
|
if (useSections) {
|
||||||
@@ -399,44 +374,6 @@ function RecipeForm({ initialRecipe, onSubmit, onCancel }: RecipeFormProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="categories">Categories</label>
|
|
||||||
<div className="tags-input-container">
|
|
||||||
<div className="tags-list">
|
|
||||||
{categories.map((category) => (
|
|
||||||
<span key={category} className="tag">
|
|
||||||
{category}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleRemoveCategory(category)}
|
|
||||||
className="tag-remove"
|
|
||||||
title="Remove category"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="tag-input-row">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="categories"
|
|
||||||
value={categoryInput}
|
|
||||||
onChange={(e) => setCategoryInput(e.target.value)}
|
|
||||||
onKeyDown={handleCategoryInputKeyDown}
|
|
||||||
placeholder="Add a category and press Enter"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleAddCategory}
|
|
||||||
className="btn-add-tag"
|
|
||||||
>
|
|
||||||
Add Category
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Image Upload (only for editing existing recipes) */}
|
{/* Image Upload (only for editing existing recipes) */}
|
||||||
{initialRecipe?.id && (
|
{initialRecipe?.id && (
|
||||||
<div className="form-group image-upload-section">
|
<div className="form-group image-upload-section">
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ function UnifiedEditRecipe() {
|
|||||||
const [cookTime, setCookTime] = useState('');
|
const [cookTime, setCookTime] = useState('');
|
||||||
const [servings, setServings] = useState('');
|
const [servings, setServings] = useState('');
|
||||||
const [cuisine, setCuisine] = useState('');
|
const [cuisine, setCuisine] = useState('');
|
||||||
const [recipeCategories, setRecipeCategories] = useState<string[]>([]);
|
|
||||||
const [categoryInput, setCategoryInput] = useState('');
|
|
||||||
const [recipeTags, setRecipeTags] = useState<string[]>([]);
|
const [recipeTags, setRecipeTags] = useState<string[]>([]);
|
||||||
const [tagInput, setTagInput] = useState('');
|
const [tagInput, setTagInput] = useState('');
|
||||||
const [availableTags, setAvailableTags] = useState<Tag[]>([]);
|
const [availableTags, setAvailableTags] = useState<Tag[]>([]);
|
||||||
@@ -92,7 +90,6 @@ function UnifiedEditRecipe() {
|
|||||||
setCookTime(loadedRecipe.cookTime?.toString() || '');
|
setCookTime(loadedRecipe.cookTime?.toString() || '');
|
||||||
setServings(loadedRecipe.servings?.toString() || '');
|
setServings(loadedRecipe.servings?.toString() || '');
|
||||||
setCuisine(loadedRecipe.cuisine || '');
|
setCuisine(loadedRecipe.cuisine || '');
|
||||||
setRecipeCategories(loadedRecipe.categories || []);
|
|
||||||
|
|
||||||
// Handle tags - API returns array of {tag: {id, name}} objects, we need string[]
|
// Handle tags - API returns array of {tag: {id, name}} objects, we need string[]
|
||||||
const tagNames = (loadedRecipe.tags as any)?.map((t: any) => t.tag?.name || t).filter(Boolean) || [];
|
const tagNames = (loadedRecipe.tags as any)?.map((t: any) => t.tag?.name || t).filter(Boolean) || [];
|
||||||
@@ -482,7 +479,6 @@ function UnifiedEditRecipe() {
|
|||||||
cookTime: cookTime ? parseInt(cookTime) : undefined,
|
cookTime: cookTime ? parseInt(cookTime) : undefined,
|
||||||
servings: servings ? parseInt(servings) : undefined,
|
servings: servings ? parseInt(servings) : undefined,
|
||||||
cuisine: cuisine || undefined,
|
cuisine: cuisine || undefined,
|
||||||
categories: recipeCategories.length > 0 ? recipeCategories : undefined,
|
|
||||||
tags: recipeTags,
|
tags: recipeTags,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -602,33 +598,6 @@ function UnifiedEditRecipe() {
|
|||||||
navigate(`/recipes/${id}`);
|
navigate(`/recipes/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Category management functions
|
|
||||||
const handleAddCategory = (categoryName: string) => {
|
|
||||||
const trimmedCategory = categoryName.trim();
|
|
||||||
if (!trimmedCategory) return;
|
|
||||||
|
|
||||||
if (recipeCategories.includes(trimmedCategory)) {
|
|
||||||
setCategoryInput('');
|
|
||||||
return; // Category already exists
|
|
||||||
}
|
|
||||||
|
|
||||||
setRecipeCategories([...recipeCategories, trimmedCategory]);
|
|
||||||
setCategoryInput('');
|
|
||||||
setHasChanges(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveCategory = (categoryToRemove: string) => {
|
|
||||||
setRecipeCategories(recipeCategories.filter(cat => cat !== categoryToRemove));
|
|
||||||
setHasChanges(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCategoryInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
handleAddCategory(categoryInput);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tag management functions
|
// Tag management functions
|
||||||
const handleAddTag = async (tagName: string) => {
|
const handleAddTag = async (tagName: string) => {
|
||||||
const trimmedTag = tagName.trim();
|
const trimmedTag = tagName.trim();
|
||||||
@@ -840,45 +809,6 @@ function UnifiedEditRecipe() {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Categories */}
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="categories">Categories</label>
|
|
||||||
<div className="tags-input-container">
|
|
||||||
<div className="tags-list">
|
|
||||||
{recipeCategories.map((category) => (
|
|
||||||
<span key={category} className="tag">
|
|
||||||
{category}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleRemoveCategory(category)}
|
|
||||||
className="tag-remove"
|
|
||||||
title="Remove category"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="tag-input-row">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="categories"
|
|
||||||
value={categoryInput}
|
|
||||||
onChange={(e) => setCategoryInput(e.target.value)}
|
|
||||||
onKeyDown={handleCategoryInputKeyDown}
|
|
||||||
placeholder="Add a category and press Enter"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleAddCategory(categoryInput)}
|
|
||||||
className="btn-add-tag"
|
|
||||||
>
|
|
||||||
Add Category
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="tags">Tags</label>
|
<label htmlFor="tags">Tags</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user