feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m31s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m31s
Homepage:
- Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch
sortiert, lädt wenn Profil aktiv ist; versteckt sonst)
- Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon
zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die
Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1
unterdrückt. Section versteckt sich, wenn die Liste leer wird.
DB:
- Neue Migration 004_recipe_hidden_from_recent.sql (+Index)
- listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE)
- setRecipeHiddenFromRecent in actions.ts
API:
- GET /api/recipes/favorites?profile_id=X
- PATCH /api/recipes/[id] akzeptiert jetzt title und/oder
hidden_from_recent (Zod-Schema mit refine)
Rezept-Detail:
- Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben
H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab.
Kein location.reload() mehr.
- RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override.
- Neue Aktionsreihenfolge:
Zeile 1: Favorit | Wunschliste | Drucken
Zeile 2: Heute gekocht | Löschen
(Umbenennen ist jetzt am Titel statt in der Leiste.)
Icons (lucide-svelte, neues Dep):
- Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header,
Rezept-Detail, Wunschliste, Header-Dropdown:
🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe,
♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check,
🍳→ChefHat, X→X
- Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo,
Lucide käme da nicht sauber rein).
- SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme.
Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
-- Let the user dismiss individual recipes from the "Zuletzt hinzugefügt"
|
||||
-- list on the homepage. The recipe itself stays searchable and fully
|
||||
-- functional — only its appearance in the "recent" list is suppressed.
|
||||
ALTER TABLE recipe ADD COLUMN hidden_from_recent INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX idx_recipe_hidden_from_recent ON recipe(hidden_from_recent, created_at);
|
||||
@@ -150,3 +150,14 @@ export function renameRecipe(
|
||||
recipeId
|
||||
);
|
||||
}
|
||||
|
||||
export function setRecipeHiddenFromRecent(
|
||||
db: Database.Database,
|
||||
recipeId: number,
|
||||
hidden: boolean
|
||||
): void {
|
||||
db.prepare('UPDATE recipe SET hidden_from_recent = ? WHERE id = ?').run(
|
||||
hidden ? 1 : 0,
|
||||
recipeId
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,8 +67,30 @@ export function listRecentRecipes(
|
||||
(SELECT AVG(stars) FROM rating WHERE recipe_id = r.id) AS avg_stars,
|
||||
(SELECT MAX(cooked_at) FROM cooking_log WHERE recipe_id = r.id) AS last_cooked_at
|
||||
FROM recipe r
|
||||
WHERE r.hidden_from_recent = 0
|
||||
ORDER BY r.created_at DESC
|
||||
LIMIT ?`
|
||||
)
|
||||
.all(limit) as SearchHit[];
|
||||
}
|
||||
|
||||
export function listFavoritesForProfile(
|
||||
db: Database.Database,
|
||||
profileId: number
|
||||
): SearchHit[] {
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT r.id,
|
||||
r.title,
|
||||
r.description,
|
||||
r.image_path,
|
||||
r.source_domain,
|
||||
(SELECT AVG(stars) FROM rating WHERE recipe_id = r.id) AS avg_stars,
|
||||
(SELECT MAX(cooked_at) FROM cooking_log WHERE recipe_id = r.id) AS last_cooked_at
|
||||
FROM recipe r
|
||||
JOIN favorite f ON f.recipe_id = r.id
|
||||
WHERE f.profile_id = ?
|
||||
ORDER BY r.title COLLATE NOCASE`
|
||||
)
|
||||
.all(profileId) as SearchHit[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user