From f95d1f3028d048c974212b12151faa8cfc2576d9 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sat, 6 Jun 2026 22:31:58 +0530 Subject: [PATCH] feat: Add search to brew logs --- src/App.jsx | 74 ++++++++++++++++++++++++++++++++++++--- src/pages/ProfilePage.jsx | 2 +- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 8ccf14b..dcea2b7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,6 @@ import { useState, useEffect, useCallback, useContext, useRef } from "react"; import { AuthContext } from "./AuthContext"; +import { Search } from "lucide-react"; // Import pages/views import Login from "./pages/Login"; @@ -72,12 +73,14 @@ export default function CoffeeLogbook() { const [selectedBean, setSelectedBean] = useState(null); const [brewFilter, setBrewFilter] = useState("all"); const [editingBean, setEditingBean] = useState(null); + const [brewSearchQuery, setBrewSearchQuery] = useState(""); const [isOnline, setIsOnline] = useState(navigator.onLine); const [syncing, setSyncing] = useState(false); const [showSyncedStatus, setShowSyncedStatus] = useState(false); useEffect(() => { loadData().then(setData); }, []); + useEffect(() => { setBrewSearchQuery(""); }, [view]); const dataRef = useRef(data); dataRef.current = data; @@ -178,7 +181,37 @@ export default function CoffeeLogbook() { const addBrew = (form) => { persist({ ...data, brewLogs: [...allLogs, { id: uid(), createdAt: Date.now(), ...form, updatedAt: new Date().toISOString(), isDeleted: false }] }); setModal(null); }; const getBeanName = (id) => allBeans.find(b => b.id === id)?.name || "Unknown Bean"; - const filteredLogs = (brewFilter === "all" ? brewLogs : brewLogs.filter(l => l.method === brewFilter)).sort((a, b) => b.createdAt - a.createdAt); + const filteredLogs = (brewFilter === "all" ? brewLogs : brewLogs.filter(l => l.method === brewFilter)) + .filter(log => { + if (!brewSearchQuery) return true; + const dateObj = new Date(log.createdAt); + const weekday = dateObj.toLocaleDateString("en-US", { weekday: "long" }).toLowerCase(); + const month = dateObj.toLocaleDateString("en-US", { month: "long" }).toLowerCase(); + const monthShort = dateObj.toLocaleDateString("en-US", { month: "short" }).toLowerCase(); + const dateNum = dateObj.getDate().toString(); + const year = dateObj.getFullYear().toString(); + + const searchableText = [ + getBeanName(log.beanId).toLowerCase(), + METHOD_LABELS[log.method]?.toLowerCase() || "", + log.recipeDetails?.toLowerCase() || "", + log.tasteNotes?.toLowerCase() || "", + log.notes?.toLowerCase() || "", + log.grindSize?.toLowerCase() || log.grind?.toLowerCase() || "", + log.waterTemp?.toLowerCase() || "", + log.brewRatio?.toLowerCase() || log.ratio?.toLowerCase() || "", + log.brewTime?.toLowerCase() || log.time?.toLowerCase() || "", + weekday, + month, + monthShort, + dateNum, + year + ].join(" "); + + const queryWords = brewSearchQuery.trim().toLowerCase().split(/\s+/); + return queryWords.every(word => searchableText.includes(word)); + }) + .sort((a, b) => b.createdAt - a.createdAt); const methodCounts = { pourover: 0, espresso: 0, coldbrew: 0 }; brewLogs.forEach(l => { if (methodCounts[l.method] !== undefined) methodCounts[l.method]++; }); @@ -292,6 +325,33 @@ export default function CoffeeLogbook() { {/* ── Brew Logs ── */} {view === "brews" && (
+ {/* Search Bar */} +
+ setBrewSearchQuery(e.target.value)} + placeholder="Search brews (e.g. June, espresso, floral...)" + className="w-full pl-10 pr-10 py-3.5 bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl text-xs placeholder-[#9C8B7A] dark:placeholder-[#C8B9A6] text-[#2C1810] dark:text-[#FAF6F1] shadow-[0_1px_3px_rgba(44,24,16,0.04)] dark:shadow-none focus:outline-none focus:border-[#8B6914] dark:focus:border-[#D4A325] transition-all" + /> + {/* Search Icon */} +
+ + + + +
+ {/* Clear button */} + {brewSearchQuery && ( + + )} +
+
{METHODS.map(m => ( @@ -302,9 +362,15 @@ export default function CoffeeLogbook() {
{filteredLogs.length === 0 ? (
-
📋
-

No logs {brewFilter !== "all" ? `for ${METHOD_LABELS[brewFilter]}` : "yet"}

-

Start brewing and log your recipes here.

+
+ {brewSearchQuery ? : 📋} +
+

+ {brewSearchQuery ? "No search results" : `No logs ${brewFilter !== "all" ? `for ${METHOD_LABELS[brewFilter]}` : "yet"}`} +

+

+ {brewSearchQuery ? "Try refining your search terms or filters." : "Start brewing and log your recipes here."} +

) : filteredLogs.map(log => ( diff --git a/src/pages/ProfilePage.jsx b/src/pages/ProfilePage.jsx index f0cc829..4e21dc7 100644 --- a/src/pages/ProfilePage.jsx +++ b/src/pages/ProfilePage.jsx @@ -80,7 +80,7 @@ export default function ProfilePage({ user, isOnline, syncing, showSyncedStatus