feat: Add search to brew logs
All checks were successful
Deploy Brew Application / deploy (push) Successful in 11s

This commit is contained in:
2026-06-06 22:31:58 +05:30
parent a27bd118e5
commit f95d1f3028
2 changed files with 71 additions and 5 deletions

View File

@@ -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" && (
<div className="animate-page-enter">
{/* Search Bar */}
<div className="mb-4 relative">
<input
type="text"
value={brewSearchQuery}
onChange={(e) => 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 */}
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#9C8B7A] dark:text-[#C8B9A6] flex items-center pointer-events-none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="opacity-70">
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</div>
{/* Clear button */}
{brewSearchQuery && (
<button
onClick={() => setBrewSearchQuery("")}
className="absolute right-3.5 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center rounded-full text-[#9C8B7A] dark:text-[#C8B9A6] hover:text-[#2C1810] dark:hover:text-[#FAF6F1] border-none bg-transparent cursor-pointer text-base font-bold transition-colors"
>
&times;
</button>
)}
</div>
<div className="flex gap-1.5 mb-4 overflow-x-auto pb-1">
<button className={filterPillCls(brewFilter === "all")} onClick={() => setBrewFilter("all")}>All</button>
{METHODS.map(m => (
@@ -302,9 +362,15 @@ export default function CoffeeLogbook() {
</div>
{filteredLogs.length === 0 ? (
<div className="text-center py-12 text-[#9C8B7A] dark:text-[#C8B9A6]">
<div className="text-4xl mb-3 opacity-50 dark:opacity-80">📋</div>
<h3 className="text-base mb-1.5 font-serif text-[#6B5744] dark:text-[#FAF6F1]">No logs {brewFilter !== "all" ? `for ${METHOD_LABELS[brewFilter]}` : "yet"}</h3>
<p className="text-[13px] leading-relaxed">Start brewing and log your recipes here.</p>
<div className="flex justify-center mb-3 text-[#9C8B7A] dark:text-[#C8B9A6] opacity-60">
{brewSearchQuery ? <Search size={36} strokeWidth={2} /> : <span className="text-4xl">📋</span>}
</div>
<h3 className="text-base mb-1.5 font-serif text-[#6B5744] dark:text-[#FAF6F1]">
{brewSearchQuery ? "No search results" : `No logs ${brewFilter !== "all" ? `for ${METHOD_LABELS[brewFilter]}` : "yet"}`}
</h3>
<p className="text-[13px] leading-relaxed">
{brewSearchQuery ? "Try refining your search terms or filters." : "Start brewing and log your recipes here."}
</p>
</div>
) : filteredLogs.map(log => (
<BrewCard key={log.id} log={log} beanName={getBeanName(log.beanId)} />