All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m14s
1) Trash-Button auf Wunschliste wieder da. Im Gegensatz zum Heart
entfernt er den Eintrag NICHT nur für das aktive Profil, sondern
löscht alle Memberships auf diesem Rezept. Bestätigungsdialog macht
das explizit ("wird für alle Profile aus der Wunschliste gestrichen").
- repository.ts: neue Funktion removeFromWishlistForAll(recipeId)
- DELETE /api/wishlist/:id?all=true → family-wide
DELETE /api/wishlist/:id?profile_id=X → nur mein Eintrag
- UI: zwei Action-Buttons untereinander (Heart, Trash)
2) wishlistStore.refresh() läuft jetzt in afterNavigate des Root-Layouts.
Vorher wurde der Badge nur aktualisiert, wenn derselbe Tab die Aktion
ausgelöst hat. Wenn ein anderer Tab / anderes Gerät etwas ändert,
bleibt der Badge stale bis zum nächsten Full-Reload. Mit afterNavigate
reicht eine Client-Navigation, um ihn zu aktualisieren — was deutlich
näher an dem liegt, was der User erwartet.
115 lines
2.9 KiB
TypeScript
115 lines
2.9 KiB
TypeScript
import type Database from 'better-sqlite3';
|
|
|
|
export type WishlistEntry = {
|
|
recipe_id: number;
|
|
title: string;
|
|
image_path: string | null;
|
|
source_domain: string | null;
|
|
added_at: string; // earliest per recipe
|
|
wanted_by_count: number;
|
|
wanted_by_names: string; // comma-joined profile names
|
|
on_my_wishlist: 0 | 1;
|
|
avg_stars: number | null;
|
|
};
|
|
|
|
export type SortKey = 'popular' | 'newest' | 'oldest';
|
|
|
|
export function listWishlist(
|
|
db: Database.Database,
|
|
activeProfileId: number | null,
|
|
sort: SortKey = 'popular'
|
|
): WishlistEntry[] {
|
|
const orderBy = {
|
|
popular: 'wanted_by_count DESC, first_added DESC',
|
|
newest: 'first_added DESC',
|
|
oldest: 'first_added ASC'
|
|
}[sort];
|
|
|
|
return db
|
|
.prepare(
|
|
`SELECT
|
|
w.recipe_id,
|
|
r.title,
|
|
r.image_path,
|
|
r.source_domain,
|
|
MIN(w.added_at) AS first_added,
|
|
MIN(w.added_at) AS added_at,
|
|
COUNT(w.profile_id) AS wanted_by_count,
|
|
COALESCE(GROUP_CONCAT(p.name, ', '), '') AS wanted_by_names,
|
|
CASE
|
|
WHEN ? IS NULL THEN 0
|
|
WHEN EXISTS (SELECT 1 FROM wishlist w2
|
|
WHERE w2.recipe_id = w.recipe_id AND w2.profile_id = ?)
|
|
THEN 1
|
|
ELSE 0
|
|
END AS on_my_wishlist,
|
|
(SELECT AVG(stars) FROM rating WHERE recipe_id = w.recipe_id) AS avg_stars
|
|
FROM wishlist w
|
|
JOIN recipe r ON r.id = w.recipe_id
|
|
LEFT JOIN profile p ON p.id = w.profile_id
|
|
GROUP BY w.recipe_id
|
|
ORDER BY ${orderBy}`
|
|
)
|
|
.all(activeProfileId, activeProfileId) as WishlistEntry[];
|
|
}
|
|
|
|
export function listWishlistProfileIds(
|
|
db: Database.Database,
|
|
recipeId: number
|
|
): number[] {
|
|
return (
|
|
db
|
|
.prepare('SELECT profile_id FROM wishlist WHERE recipe_id = ?')
|
|
.all(recipeId) as { profile_id: number }[]
|
|
).map((r) => r.profile_id);
|
|
}
|
|
|
|
export function countWishlistRecipes(db: Database.Database): number {
|
|
const row = db
|
|
.prepare('SELECT COUNT(DISTINCT recipe_id) AS n FROM wishlist')
|
|
.get() as { n: number };
|
|
return row.n;
|
|
}
|
|
|
|
export function addToWishlist(
|
|
db: Database.Database,
|
|
recipeId: number,
|
|
profileId: number
|
|
): void {
|
|
db.prepare(
|
|
`INSERT INTO wishlist(recipe_id, profile_id)
|
|
VALUES (?, ?)
|
|
ON CONFLICT(recipe_id, profile_id) DO NOTHING`
|
|
).run(recipeId, profileId);
|
|
}
|
|
|
|
export function removeFromWishlist(
|
|
db: Database.Database,
|
|
recipeId: number,
|
|
profileId: number
|
|
): void {
|
|
db.prepare('DELETE FROM wishlist WHERE recipe_id = ? AND profile_id = ?').run(
|
|
recipeId,
|
|
profileId
|
|
);
|
|
}
|
|
|
|
export function removeFromWishlistForAll(
|
|
db: Database.Database,
|
|
recipeId: number
|
|
): void {
|
|
db.prepare('DELETE FROM wishlist WHERE recipe_id = ?').run(recipeId);
|
|
}
|
|
|
|
export function isOnMyWishlist(
|
|
db: Database.Database,
|
|
recipeId: number,
|
|
profileId: number
|
|
): boolean {
|
|
return (
|
|
db
|
|
.prepare('SELECT 1 AS ok FROM wishlist WHERE recipe_id = ? AND profile_id = ?')
|
|
.get(recipeId, profileId) !== undefined
|
|
);
|
|
}
|