diff --git a/src/application/pages/app/archive.astro b/src/application/pages/app/archive.astro index c35c20c..e0266e2 100644 --- a/src/application/pages/app/archive.astro +++ b/src/application/pages/app/archive.astro @@ -1,9 +1,169 @@ --- -export const prerender=false; +export const prerender = false; import BaseLayout from "../../../layouts/BaseLayout.astro"; -import {openDatabase,refreshSiteProjection} from "../../../lib/database"; -const database=openDatabase({readOnly:false});if(!database)return Astro.redirect("/app/",303); -if(Astro.request.method==="POST"){const form=await Astro.request.formData(),type=String(form.get("type")),id=String(form.get("id"));const tables:{[key:string]:string}={recipe:"recipes",ingredient:"ingredients",book:"collections"};if(tables[type])database.prepare(`UPDATE ${tables[type]} SET deleted_at=NULL${type==="ingredient"?",status='active'":""} WHERE id=?`).run(id);refreshSiteProjection(database);database.close();return Astro.redirect("/app/archive/",303);} -const items=[...(database.prepare("SELECT id,title name,deleted_at FROM recipes WHERE deleted_at IS NOT NULL").all() as any[]).map(x=>({...x,type:"recipe"})),...(database.prepare("SELECT id,name,deleted_at FROM ingredients WHERE deleted_at IS NOT NULL").all() as any[]).map(x=>({...x,type:"ingredient"})),...(database.prepare("SELECT id,name,deleted_at FROM collections WHERE deleted_at IS NOT NULL").all() as any[]).map(x=>({...x,type:"book"}))].sort((a,b)=>a.name.localeCompare(b.name));database.close(); +import DetailUtility from "../../../components/DetailUtility.astro"; +import { openDatabase, refreshSiteProjection } from "../../../lib/database"; +import { titleCase } from "../../../lib/format"; + +const database = openDatabase({ readOnly: false }); +if (!database) return Astro.redirect("/app/", 303); + +if (Astro.request.method === "POST") { + const form = await Astro.request.formData(); + const type = String(form.get("type")); + const id = String(form.get("id")); + const tables: Record = { recipe: "recipes", ingredient: "ingredients", book: "collections" }; + if (tables[type]) { + database.prepare(`UPDATE ${tables[type]} SET deleted_at=NULL${type === "ingredient" ? ",status='active'" : ""} WHERE id=?`).run(id); + refreshSiteProjection(database); + } + database.close(); + return Astro.redirect("/app/archive/", 303); +} + +const recipes = (database.prepare("SELECT id, title AS name, deleted_at FROM recipes WHERE deleted_at IS NOT NULL").all() as any[]).map((x) => ({ ...x, type: "recipe" as const })); +const ingredients = (database.prepare("SELECT id, name, deleted_at FROM ingredients WHERE deleted_at IS NOT NULL").all() as any[]).map((x) => ({ ...x, type: "ingredient" as const })); +const books = (database.prepare("SELECT id, name, deleted_at FROM collections WHERE deleted_at IS NOT NULL").all() as any[]).map((x) => ({ ...x, type: "book" as const })); + +const allItems = [...recipes, ...ingredients, ...books].sort((a, b) => a.name.localeCompare(b.name)); +database.close(); + +const requestedFilter = Astro.url.searchParams.get("type") ?? "all"; +const query = (Astro.url.searchParams.get("q") ?? "").trim().toLowerCase(); + +const filteredItems = allItems.filter((item) => { + if (requestedFilter !== "all" && item.type !== requestedFilter) return false; + if (query && !item.name.toLowerCase().includes(query)) return false; + return true; +}); + +function formatDeleteDate(dateStr: string | null) { + if (!dateStr) return ""; + try { + const d = new Date(dateStr.includes("Z") || dateStr.includes("T") ? dateStr : `${dateStr.replace(" ", "T")}Z`); + if (isNaN(d.getTime())) return dateStr; + return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); + } catch { + return dateStr; + } +} --- -
← All items

Archive

Restore recipes, ingredients, and recipe books removed from the active workspace.

{items.length?items.map(item=>
{item.type==="recipe"?"▦":item.type==="book"?"▣":"●"}{item.name}{item.type} · deleted {item.deleted_at}
):

Nothing has been archived.

}
+ + +
+ + +
+
+
+ +

Archive

+

+ Restore recipes, ingredients, and recipe books removed from the active workspace. +

+
+
+ + + + {filteredItems.length > 0 ? ( +
+
+ Type + Item Name + Deleted Date + Action +
+ +
+ {filteredItems.map((item) => ( +
+
+ + {item.type === "recipe" && ( + + + + )} + {item.type === "ingredient" && ( + + + + )} + {item.type === "book" && ( + + + + )} + +
+ +
+ {titleCase(item.name)} + {item.type} +
+ +
+ Deleted {formatDeleteDate(item.deleted_at)} +
+ +
+
+ + + +
+
+
+ ))} +
+
+ ) : ( +
+
+ + + + + +
+

Nothing in the archive

+

Archived recipes, ingredients, and recipe books will appear here and can be restored anytime.

+ Return to workspace +
+ )} +
+
+
diff --git a/src/application/pages/app/recipe-books/[id].astro b/src/application/pages/app/recipe-books/[id].astro index ab46e26..f69cdca 100644 --- a/src/application/pages/app/recipe-books/[id].astro +++ b/src/application/pages/app/recipe-books/[id].astro @@ -1,19 +1,235 @@ --- -export const prerender=false; +export const prerender = false; import BaseLayout from "../../../../layouts/BaseLayout.astro"; import DetailUtility from "../../../../components/DetailUtility.astro"; -import {readOnlyMode} from "../../../../lib/runtime"; -import {openDatabase,refreshSiteProjection} from "../../../../lib/database"; -const id=Astro.params.id!,editing=!readOnlyMode&&Astro.url.searchParams.get("edit")==="1"; -const database=openDatabase({readOnly:false});if(!database)return Astro.redirect("/app/?error=database-missing",303); -let error=""; -if(Astro.request.method==="POST")try{const form=await Astro.request.formData(),intent=String(form.get("intent")); - if(intent==="details"){const name=String(form.get("name")??"").trim();if(!name)throw new Error("Name is required.");database.prepare("UPDATE collections SET name=?,description=? WHERE id=?").run(name,String(form.get("description")??"").trim()||null,id);} - if(intent==="membership"){const selected=new Set(form.getAll("recipe_id").map(String));database.exec("BEGIN IMMEDIATE");try{database.prepare("DELETE FROM collection_recipes WHERE collection_id=?").run(id);const insert=database.prepare("INSERT INTO collection_recipes(collection_id,recipe_id,position) VALUES (?,?,?)");[...selected].forEach((recipeId,index)=>insert.run(id,recipeId,index+1));database.exec("COMMIT");}catch(cause){database.exec("ROLLBACK");throw cause;}} - refreshSiteProjection(database);database.close();return Astro.redirect(`/app/recipe-books/${id}/`,303); -}catch(cause){error=cause instanceof Error?cause.message:"Unable to save recipe book.";} -const book=database.prepare("SELECT * FROM collections WHERE id=? AND deleted_at IS NULL").get(id) as any;if(!book){database.close();return new Response("Recipe book not found",{status:404});} -const recipes=database.prepare("SELECT r.id,r.title,cr.position,cr.recipe_id IS NOT NULL included FROM recipes r LEFT JOIN collection_recipes cr ON cr.recipe_id=r.id AND cr.collection_id=? WHERE r.deleted_at IS NULL ORDER BY coalesce(cr.position,999999),r.title").all(id) as any[]; +import { readOnlyMode } from "../../../../lib/runtime"; +import { openDatabase, refreshSiteProjection } from "../../../../lib/database"; + +const id = Astro.params.id!; +const editing = !readOnlyMode && Astro.url.searchParams.get("edit") === "1"; +const database = openDatabase({ readOnly: false }); +if (!database) return Astro.redirect("/app/?error=database-missing", 303); + +let error = ""; +if (Astro.request.method === "POST") { + try { + const form = await Astro.request.formData(); + const intent = String(form.get("intent") ?? "save"); + + if (intent === "save" || intent === "details") { + const name = String(form.get("name") ?? "").trim(); + const description = String(form.get("description") ?? "").trim() || null; + if (!name) throw new Error("Name is required."); + database.prepare("UPDATE collections SET name=?, description=? WHERE id=?").run(name, description, id); + } + + if (intent === "save" || intent === "membership") { + const selected = new Set(form.getAll("recipe_id").map(String)); + database.exec("BEGIN IMMEDIATE"); + try { + database.prepare("DELETE FROM collection_recipes WHERE collection_id=?").run(id); + const insert = database.prepare("INSERT INTO collection_recipes(collection_id, recipe_id, position) VALUES (?, ?, ?)"); + [...selected].forEach((recipeId, index) => insert.run(id, recipeId, index + 1)); + database.exec("COMMIT"); + } catch (cause) { + database.exec("ROLLBACK"); + throw cause; + } + } + + refreshSiteProjection(database); + database.close(); + return Astro.redirect(`/app/recipe-books/${id}/`, 303); + } catch (cause) { + error = cause instanceof Error ? cause.message : "Unable to save recipe book."; + } +} + +const book = database.prepare("SELECT * FROM collections WHERE id=? AND deleted_at IS NULL").get(id) as any; +if (!book) { + database.close(); + return new Response("Recipe book not found", { status: 404 }); +} + +const recipes = database.prepare(` + SELECT r.id, r.title, cr.position, (cr.recipe_id IS NOT NULL) AS included + FROM recipes r + LEFT JOIN collection_recipes cr ON cr.recipe_id = r.id AND cr.collection_id = ? + WHERE r.deleted_at IS NULL + ORDER BY coalesce(cr.position, 999999), r.title +`).all(id) as any[]; + +const includedRecipes = recipes.filter((r) => r.included); database.close(); --- -

← Recipe books

{book.name}

{recipes.filter(x=>x.included).length} recipes

{!readOnlyMode&&{editing?"✓ Done":"✎ Edit"}}
{error&&

{error}

}
{editing&&
}

Recipes

{editing?"Choose the recipes included in this book.":book.description}

{editing&&}
{recipes.filter(recipe=>editing||recipe.included).map(recipe=>)}
+ + +
+ + +
+ {error &&
{error}
} + +
+
+ +

{book.name}

+
+ + {includedRecipes.length} {includedRecipes.length === 1 ? "recipe" : "recipes"} + + {book.description && {book.description}} +
+
+ + {!readOnlyMode && ( + + )} +
+ + {editing ? ( +
+ + +
+

Book Details

+
+ + +
+
+ +
+
+
+

Select Recipes

+

+ Choose the recipes included in this book ({includedRecipes.length} currently selected) +

+
+
+ +
+ {recipes.map((recipe) => ( + + ))} +
+
+ + +
+ ) : ( +
+ {includedRecipes.length > 0 ? ( +
+
+ Included Recipes + {includedRecipes.length} {includedRecipes.length === 1 ? "recipe" : "recipes"} +
+
+ {includedRecipes.map((recipe) => ( + + ))} +
+
+ ) : ( +
+ +

No recipes in this book yet

+

Organize your recipes by adding them to this book.

+ {!readOnlyMode && ( + + ✎ Add recipes + + )} +
+ )} +
+ )} +
+
+
diff --git a/src/application/pages/app/recipe-books/new.astro b/src/application/pages/app/recipe-books/new.astro index e9b3d84..f6d3ce9 100644 --- a/src/application/pages/app/recipe-books/new.astro +++ b/src/application/pages/app/recipe-books/new.astro @@ -1,8 +1,93 @@ --- -export const prerender=false; +export const prerender = false; import BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { openDatabase,refreshSiteProjection } from "../../../../lib/database"; -let error:string|undefined; -if(Astro.request.method==="POST"){const database=openDatabase({readOnly:false});if(!database)return Astro.redirect("/app/?error=database-missing",303);try{const form=await Astro.request.formData();const name=String(form.get("name")??"").trim();const description=String(form.get("description")??"").trim()||null;if(!name)throw new Error("Name is required.");const base=name.toLocaleLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")||"recipe_book";let id=base,suffix=2;while(database.prepare("SELECT 1 FROM collections WHERE id=?").get(id))id=`${base}_${suffix++}`;database.prepare("INSERT INTO collections(id,name,description,source_json) VALUES (?,?,?,'{}')").run(id,name,description);refreshSiteProjection(database);database.close();return Astro.redirect("/app/?type=book",303);}catch(cause){error=cause instanceof Error?cause.message:"Unable to create recipe book.";database.close();}} +import DetailUtility from "../../../../components/DetailUtility.astro"; +import { openDatabase, refreshSiteProjection } from "../../../../lib/database"; + +let error: string | undefined; + +if (Astro.request.method === "POST") { + const database = openDatabase({ readOnly: false }); + if (!database) return Astro.redirect("/app/?error=database-missing", 303); + + try { + const form = await Astro.request.formData(); + const name = String(form.get("name") ?? "").trim(); + const description = String(form.get("description") ?? "").trim() || null; + if (!name) throw new Error("Name is required."); + + const base = name.toLocaleLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "recipe_book"; + let id = base; + let suffix = 2; + while (database.prepare("SELECT 1 FROM collections WHERE id=?").get(id)) { + id = `${base}_${suffix++}`; + } + + database.prepare("INSERT INTO collections(id, name, description, source_json) VALUES (?, ?, ?, '{}')").run(id, name, description); + refreshSiteProjection(database); + database.close(); + return Astro.redirect(`/app/recipe-books/${id}/`, 303); + } catch (cause) { + error = cause instanceof Error ? cause.message : "Unable to create recipe book."; + database.close(); + } +} --- -

Recipe books

New recipe book

Create a collection for organizing recipes.

Recipe book details{error&&
{error}
}
Cancel
+ + +
+ + +
+ {error &&
{error}
} + +
+
+ +

New recipe book

+

Create a collection for organizing recipes.

+
+
+ +
+
+

Recipe Book Details

+
+ + +
+
+ +
+ + Cancel +
+
+
+
+
diff --git a/src/application/pages/tools/purchasing-review.astro b/src/application/pages/tools/purchasing-review.astro index dbc70c3..fd3463b 100644 --- a/src/application/pages/tools/purchasing-review.astro +++ b/src/application/pages/tools/purchasing-review.astro @@ -1,9 +1,53 @@ --- -export const prerender = true; -import fs from "node:fs"; import path from "node:path"; import YAML from "yaml"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; import PurchasingReview from "../../../components/PurchasingReview"; +export const prerender = false; +import fs from "node:fs"; +import path from "node:path"; +import YAML from "yaml"; +import BaseLayout from "../../../layouts/BaseLayout.astro"; +import DetailUtility from "../../../components/DetailUtility.astro"; +import PurchasingReview from "../../../components/PurchasingReview"; import { ingredients } from "../../../lib/data"; -const file=path.resolve(process.cwd(),"generated/receipt-product-candidates.yaml"); const data=fs.existsSync(file)?YAML.parse(fs.readFileSync(file,"utf8")):null; -const ingredientOptions=[...ingredients.values()].map(({id,name})=>({id,name})).sort((a,b)=>a.name.localeCompare(b.name)); + +const file = path.resolve(process.cwd(), "generated/receipt-product-candidates.yaml"); +const data = fs.existsSync(file) ? YAML.parse(fs.readFileSync(file, "utf8")) : null; +const ingredientOptions = [...ingredients.values()] + .map(({ id, name }) => ({ id, name })) + .sort((a, b) => a.name.localeCompare(b.name)); --- -

Local data tool

Receipt product review

Link actual Walmart and Sam's Club products to canonical ingredients.

{data?:
Run scripts/receipt-products propose, then rebuild.
}
+ + +
+ + +
+
+ +

Receipt product review

+

Link Walmart and Sam's Club purchase products to canonical formulation ingredients.

+
+ + {data ? ( + + ) : ( +
+ + + + + +
+ No candidates generated yet +

Run scripts/receipt-products propose to parse receipt files and extract product candidates.

+
+
+ )} +
+
+
diff --git a/src/components/PurchasingReview.tsx b/src/components/PurchasingReview.tsx index f656b52..63ecb5e 100644 --- a/src/components/PurchasingReview.tsx +++ b/src/components/PurchasingReview.tsx @@ -1,15 +1,292 @@ import { useEffect, useMemo, useState } from "preact/hooks"; -type Candidate={ingredient_id:string;name:string;score:number}; -type Product={supplier_id:string;supplier_sku:string;name:string;url?:string;package?:{quantity:number;unit_id:string};prices:Array<{amount:number;effective_at:string}>;ingredient_candidates:Candidate[]}; -type Props={products:Product[];ingredients:Array<{id:string;name:string}>}; type Decisions=Record; -const STORAGE_KEY="recipe-book-purchasing-decisions-v1"; -export default function PurchasingReview({products,ingredients}:Props){ - const [decisions,setDecisions]=useState({}); const [query,setQuery]=useState(""); const [unresolved,setUnresolved]=useState(true); - useEffect(()=>{try{setDecisions(JSON.parse(localStorage.getItem(STORAGE_KEY)??"{}"))}catch{}},[]); - const choose=(key:string,value:string|null)=>{const next={...decisions,[key]:value};setDecisions(next);localStorage.setItem(STORAGE_KEY,JSON.stringify(next))}; - const visible=useMemo(()=>products.filter(p=>{const key=`${p.supplier_id}:${p.supplier_sku}`;return p.name.toLowerCase().includes(query.toLowerCase())&&(!unresolved||!(key in decisions))}),[products,query,unresolved,decisions]); - const download=()=>{const url=URL.createObjectURL(new Blob([JSON.stringify({schema_version:1,generated_at:new Date().toISOString(),decisions},null,2)],{type:"application/json"}));const anchor=document.createElement("a");anchor.href=url;anchor.download="purchasing-decisions.json";anchor.click();URL.revokeObjectURL(url)}; - return
{Object.keys(decisions).length} / {products.length} reviewedProducts without explicit package sizes cannot be imported yet
setQuery(e.currentTarget.value)}/>
- {visible.map(product=>{const key=`${product.supplier_id}:${product.supplier_sku}`;return
{product.name}

{product.supplier_id.replace("_"," ")} · SKU {product.supplier_sku} · {product.package?`${product.package.quantity} ${product.package.unit_id}`:"package unknown"} · latest ${product.prices.at(-1)?.amount.toFixed(2)}

{product.ingredient_candidates.map(candidate=>)}{product.url&&

Inspect product ↗

}
})} -
; + +type Candidate = { + ingredient_id: string; + name: string; + score: number; +}; + +type Product = { + supplier_id: string; + supplier_sku: string; + name: string; + url?: string; + package?: { + quantity: number; + unit_id: string; + }; + prices: Array<{ + amount: number; + effective_at: string; + }>; + ingredient_candidates: Candidate[]; +}; + +type Props = { + products: Product[]; + ingredients: Array<{ id: string; name: string }>; +}; + +type Decisions = Record; + +const STORAGE_KEY = "recipe-book-purchasing-decisions-v1"; + +export default function PurchasingReview({ products, ingredients }: Props) { + const [decisions, setDecisions] = useState({}); + const [query, setQuery] = useState(""); + const [unresolved, setUnresolved] = useState(true); + + useEffect(() => { + try { + setDecisions(JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "{}")); + } catch {} + }, []); + + const choose = (key: string, value: string | null) => { + const next = { ...decisions, [key]: value }; + setDecisions(next); + localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); + }; + + const visible = useMemo(() => { + return products.filter((p) => { + const key = `${p.supplier_id}:${p.supplier_sku}`; + const matchesQuery = p.name.toLowerCase().includes(query.toLowerCase()) || + p.supplier_sku.toLowerCase().includes(query.toLowerCase()); + const matchesResolution = !unresolved || !(key in decisions); + return matchesQuery && matchesResolution; + }); + }, [products, query, unresolved, decisions]); + + const reviewedCount = Object.keys(decisions).length; + const progressPercent = products.length > 0 ? Math.round((reviewedCount / products.length) * 100) : 0; + + const download = () => { + const url = URL.createObjectURL( + new Blob( + [ + JSON.stringify( + { + schema_version: 1, + generated_at: new Date().toISOString(), + decisions, + }, + null, + 2 + ), + ], + { type: "application/json" } + ) + ); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = "purchasing-decisions.json"; + anchor.click(); + URL.revokeObjectURL(url); + }; + + const supplierLabel = (id: string) => { + if (id === "walmart") return "Walmart"; + if (id === "sams_club") return "Sam's Club"; + return id.replace("_", " "); + }; + + return ( +
+
+
+
+ {reviewedCount} / {products.length} + reviewed ({progressPercent}%) +
+ Products without explicit package sizes cannot be imported automatically. +
+ +
+ + + + + +
+
+ + {visible.length > 0 ? ( +
+ {visible.map((product) => { + const key = `${product.supplier_id}:${product.supplier_sku}`; + const currentDecision = decisions[key]; + const isResolved = key in decisions; + const latestPrice = product.prices.at(-1)?.amount; + + return ( +
+
+
+
+ + {supplierLabel(product.supplier_id)} + + SKU #{product.supplier_sku} + {product.package && ( + + {product.package.quantity} {product.package.unit_id.replace("_", " ")} + + )} + {latestPrice != null && ( + ${latestPrice.toFixed(2)} + )} +
+

{product.name}

+
+ + {product.url && ( + + Inspect product + + + + + + + )} +
+ +
+ Select ingredient mapping: + +
+ {product.ingredient_candidates.map((candidate) => { + const isSelected = currentDecision === candidate.ingredient_id; + const matchPercent = Math.round(candidate.score * 100); + + return ( + + ); + })} + +
+ +
+ + +
+
+
+ ); + })} +
+ ) : ( +
+
+ + + + +
+

No products to review

+

+ {unresolved + ? "All candidate products have been reviewed! Uncheck 'Unresolved only' to inspect past decisions." + : "No products matched your search query."} +

+ {query && ( + + )} +
+ )} +
+ ); } diff --git a/src/styles/global.css b/src/styles/global.css index 5a2cb6f..1075c5b 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -647,8 +647,66 @@ input[type="search"]::-webkit-search-results-decoration, .application-body .detail-actions-menu form { width:100% !important; max-width:100% !important; flex:none !important; min-width:0 !important; margin:0 !important; padding:0 !important; } .application-body .detail-actions-menu button { display:block; width:100% !important; max-width:100% !important; min-height:44px; padding:10px 20px; color:#050841; background:transparent; border:0; border-radius:0; cursor:pointer; font-size:15px; font-weight:500; text-align:left; white-space:nowrap; box-sizing:border-box; } .application-body .detail-actions-menu button:hover { background:#f1f5fe; } -.application-body .book-workspace { width:min(900px,calc(100% - 48px)); min-height:600px; margin:0 auto; padding:42px 0; }.application-body .book-details-form { display:grid; grid-template-columns:1fr 1.4fr auto; align-items:end; gap:12px; margin-bottom:32px; }.application-body .book-details-form label { display:grid; gap:5px; }.application-body .book-details-form input { padding:9px; }.application-body .book-details-form button,.application-body .book-membership header button { padding:9px 14px; color:#fff; background:#3d5df6; border:0; }.application-body .book-membership > header { display:flex; justify-content:space-between; align-items:start; padding-bottom:18px; border-bottom:1px solid #eeeef3; }.application-body .book-membership h2 { margin:0; font-size:20px; }.application-body .book-membership p { margin:4px 0 0; color:#a5a9c1; }.application-body .book-recipe-row { display:grid; grid-template-columns:24px 28px 1fr; align-items:center; gap:12px; min-height:60px; border-bottom:1px solid #eeeef3; }.application-body .book-recipe-row > a { display:flex; flex-direction:column; text-decoration:none; }.application-body .book-recipe-row small { color:#a5a9c1; } -.application-body .book-membership.read-only .book-recipe-row { grid-template-columns:28px minmax(0,1fr); } +/* Recipe Books */ +.application-body .recipe-book-page { min-height: 100vh; background: #f3f3f3; padding-top: 48px; } +.application-body .recipe-book-workspace { width: min(1120px, calc(100% - 48px)); margin: 0 auto; padding: 28px 0 64px; } +.application-body .recipe-book-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 28px; } +.application-body .recipe-book-breadcrumbs { margin-bottom: 8px; } +.application-body .recipe-book-breadcrumbs a { display: inline-flex; align-items: center; gap: 4px; color: #3d5df6; font-size: 14px; font-weight: 500; text-decoration: none; transition: color 0.15s ease; } +.application-body .recipe-book-breadcrumbs a:hover { color: #202962; } +.application-body .recipe-book-header h1 { margin: 0 0 6px; color: #050841; font-size: 28px; font-weight: 700; letter-spacing: -0.015em; } +.application-body .recipe-book-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; } +.application-body .recipe-book-count-badge { display: inline-flex; align-items: center; padding: 3px 10px; background: #fff; border: 1px solid #dfe3ec; border-radius: 999px; color: #687086; font-size: 13px; font-weight: 500; } +.application-body .recipe-book-count-badge .count-number { color: #3d5df6; font-weight: 700; margin-right: 4px; } +.application-body .recipe-book-desc, .application-body .recipe-book-subtitle { color: #8b93a7; font-size: 14px; font-weight: 400; margin: 0; } +.application-body .recipe-book-header-actions { display: flex; align-items: center; gap: 10px; } +.application-body .book-action-btn { display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 16px; color: #3d5df6; background: #fff; border: 1px solid #dfe3ec; border-radius: 100px; font-size: 14px; font-weight: 500; text-decoration: none; box-shadow: 0 1px 2px rgba(5,8,65,0.04); transition: all 0.15s ease; } +.application-body .book-action-btn:hover { background: #f7f9fd; border-color: #bcc6e5; } +.application-body .book-action-btn.active { color: #687086; } +.application-body .book-notice { margin-bottom: 20px; padding: 12px 16px; background: #fff1f1; border: 1px solid #f5cece; border-radius: 6px; color: #8d2e2e; font-size: 14px; } +.application-body .book-directory-table { background: #fff; border: 1px solid #eef0f5; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.04); } +.application-body .book-directory-toolbar { display: flex; align-items: center; justify-content: space-between; min-height: 48px; padding: 0 20px; background: #fbfbfb; border-bottom: 1px solid #f3f3f3; } +.application-body .book-toolbar-title { color: #050841; font-size: 14px; font-weight: 600; } +.application-body .book-toolbar-count { color: #8b93a7; font-size: 13px; font-weight: 400; } +.application-body .book-recipe-list { display: flex; flex-direction: column; } +.application-body .book-recipe-row { display: grid; grid-template-columns: 36px minmax(0, 1fr) 36px; align-items: center; gap: 12px; min-height: 58px; padding: 0 20px; border-bottom: 1px solid #f3f3f3; transition: background-color 0.12s ease; } +.application-body .book-recipe-row:last-child { border-bottom: 0; } +.application-body .book-recipe-row:hover { background: #fbfbfb; } +.application-body .book-recipe-icon { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; background: #3c4679; border-radius: 50%; color: #fff; } +.application-body .book-recipe-title { color: #050841; font-size: 15px; font-weight: 500; text-decoration: none; transition: color 0.12s ease; } +.application-body .book-recipe-title:hover { color: #3d5df6; } +.application-body .book-recipe-arrow { display: grid; place-items: center; width: 28px; height: 28px; color: #a5a9c1; border-radius: 50%; text-decoration: none; transition: all 0.12s ease; } +.application-body .book-recipe-row:hover .book-recipe-arrow { color: #3d5df6; background: #f1f5fe; } +.application-body .book-empty-state, .application-body .archive-empty-state, .application-body .purchasing-empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 64px 24px; background: #fff; border: 1px solid #eef0f5; border-radius: 6px; text-align: center; } +.application-body .book-empty-state .empty-icon { display: grid; place-items: center; width: 54px; height: 54px; margin-bottom: 16px; background: #f1f5fe; border-radius: 50%; color: #3d5df6; font-size: 24px; } +.application-body .book-empty-state h3, .application-body .archive-empty-state h3, .application-body .purchasing-empty-state h3 { margin: 0 0 6px; color: #050841; font-size: 18px; font-weight: 600; } +.application-body .book-empty-state p, .application-body .archive-empty-state p, .application-body .purchasing-empty-state p { margin: 0 0 20px; color: #8b93a7; font-size: 14px; max-width: 420px; } +.application-body .book-add-recipes-btn, .application-body .archive-back-btn, .application-body .purchasing-reset-btn { display: inline-flex; align-items: center; gap: 6px; min-height: 38px; padding: 0 20px; background: #3d5df6; color: #fff; border: 0; border-radius: 100px; font-size: 14px; font-weight: 500; text-decoration: none; cursor: pointer; box-shadow: 0 2px 6px rgba(61, 93, 246, 0.28); transition: background 0.15s ease; } +.application-body .book-add-recipes-btn:hover, .application-body .archive-back-btn:hover, .application-body .purchasing-reset-btn:hover { background: #2b4be0; } + +/* Recipe Book Editing */ +.application-body .book-edit-form { display: flex; flex-direction: column; gap: 20px; } +.application-body .book-edit-card { padding: 24px; background: #fff; border: 1px solid #eef0f5; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); } +.application-body .book-edit-card h2 { margin: 0 0 16px; color: #050841; font-size: 18px; font-weight: 600; } +.application-body .book-edit-card-header { margin-bottom: 16px; } +.application-body .section-subtitle { margin: 4px 0 0; color: #8b93a7; font-size: 14px; } +.application-body .book-field-group { display: flex; flex-direction: column; gap: 16px; } +.application-body .book-field-group label { display: flex; flex-direction: column; gap: 6px; } +.application-body .field-label { color: #050841; font-size: 13px; font-weight: 600; } +.application-body .book-input, .application-body .book-textarea { width: 100%; padding: 10px 14px; background: #fff; border: 1px solid #dfe3ec; border-radius: 4px; color: #050841; font-family: var(--meez-font-sans); font-size: 15px; font-weight: 400; outline: none; box-sizing: border-box; transition: border-color 0.15s ease, box-shadow 0.15s ease; } +.application-body .book-input:focus, .application-body .book-textarea:focus { border-color: #3d5df6; box-shadow: 0 0 0 2px rgba(61, 93, 246, 0.15); } +.application-body .book-recipe-checklist { display: flex; flex-direction: column; gap: 4px; max-height: 480px; overflow-y: auto; padding-right: 4px; } +.application-body .book-checklist-item { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 8px 14px; background: #fff; border: 1px solid #f3f3f3; border-radius: 6px; cursor: pointer; transition: background 0.12s ease, border-color 0.12s ease; } +.application-body .book-checklist-item:hover { background: #f7f9fd; border-color: #e4e7ed; } +.application-body .book-checklist-item:has(.book-checkbox:checked) { background: #f1f5fe; border-color: #cfd5fa; } +.application-body .book-checkbox { width: 18px; height: 18px; margin: 0; accent-color: #3d5df6; cursor: pointer; } +.application-body .book-checklist-label { display: flex; align-items: center; gap: 8px; flex: 1; } +.application-body .book-checklist-label strong { color: #050841; font-size: 14px; font-weight: 500; } +.application-body .book-edit-actions { display: flex; align-items: center; gap: 16px; padding: 12px 0; } +.application-body .book-save-btn { min-height: 40px; padding: 0 24px; background: #3d5df6; color: #fff; border: 0; border-radius: 100px; font-family: var(--meez-font-sans); font-size: 15px; font-weight: 500; cursor: pointer; box-shadow: 0 2px 6px rgba(61, 93, 246, 0.28); transition: background 0.15s ease; } +.application-body .book-save-btn:hover { background: #2b4be0; } +.application-body .book-cancel-link { color: #687086; font-size: 14px; font-weight: 500; text-decoration: none; padding: 8px 12px; transition: color 0.12s ease; } +.application-body .book-cancel-link:hover { color: #050841; } .application-body .recipe-additional-editor { width:min(800px,calc(100% - 76px)); margin:36px 38px; padding-top:28px; border-top:1px solid #eeeef3; }.application-body .recipe-additional-editor > form { display:grid; gap:14px; }.application-body .recipe-additional-editor label { display:grid; gap:5px; }.application-body .recipe-additional-editor input,.application-body .recipe-additional-editor textarea,.application-body .recipe-additional-editor select { padding:9px; }.application-body .recipe-additional-editor fieldset { display:grid; grid-template-columns:120px 120px 1fr; gap:10px; border:1px solid #eeeef3; }.application-body .recipe-additional-editor button { justify-self:start; padding:9px 14px; color:#fff; background:#3d5df6; border:0; } .application-body .nutrition-ingredient-list { margin-top:16px; border-top:1px solid #eeeef3; } .application-body .nutrition-list-heading { display:flex; justify-content:space-between; gap:20px; padding:14px 4px 10px; color:#202962; } @@ -670,11 +728,102 @@ input[type="search"]::-webkit-search-results-decoration, .application-body .calculator-heading .input-with-unit > span { display:grid; place-items:center; height:100%; color:#647df8; border-left:1px solid #eeeef3; font-size:14px; font-weight:600; } .application-body .calculator-heading .recipe-scale-control select { width:100%; height:38px; padding:0 6px; color:#647df8; background:#fff; border:0; border-left:1px solid #eeeef3; border-radius:0; outline:0; font-size:13px; font-weight:600; cursor:pointer; text-align:center; } .application-body .workspace-pills > a.archive-link { margin-left:auto; color:#a5a9c1; background:transparent; border-color:transparent; } -.application-body .archive-workspace h1 { margin:8px 0; font-size:28px; } -.application-body .archive-workspace header p { color:#a5a9c1; } -.application-body .archive-row { display:grid; grid-template-columns:28px 1fr auto; align-items:center; gap:12px; min-height:60px; border-bottom:1px solid #eeeef3; } -.application-body .archive-row small { display:block; color:#a5a9c1; } -.application-body .archive-row button { padding:7px 12px; color:#3d5df6; background:#fff; border:1px solid #cfd5fa; } + +/* Archive Workspace */ +.application-body .archive-page { min-height: 100vh; background: #f3f3f3; padding-top: 48px; } +.application-body .archive-workspace { width: min(1120px, calc(100% - 48px)); margin: 0 auto; padding: 28px 0 64px; } +.application-body .archive-header { margin-bottom: 24px; } +.application-body .archive-breadcrumbs { margin-bottom: 8px; } +.application-body .archive-breadcrumbs a { display: inline-flex; align-items: center; gap: 4px; color: #3d5df6; font-size: 14px; font-weight: 500; text-decoration: none; } +.application-body .archive-header h1 { margin: 0 0 6px; color: #050841; font-size: 28px; font-weight: 700; letter-spacing: -0.015em; } +.application-body .archive-subtitle { color: #8b93a7; font-size: 14px; margin: 0; } +.application-body .archive-toolbar { margin-bottom: 16px; } +.application-body .archive-filter-chips { display: flex; align-items: center; gap: 8px; overflow-x: auto; scrollbar-width: none; } +.application-body .archive-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 6px 14px; background: #fff; border: 1px solid #ececec; border-radius: 999px; color: rgba(0, 0, 0, 0.87); font-size: 14px; font-weight: 400; text-decoration: none; white-space: nowrap; transition: all 0.15s ease; } +.application-body .archive-chip:hover { background: #fff; border-color: #cfd5fa; } +.application-body .archive-chip.active { background: #DBE4FF; border-color: #DBE4FF; } +.application-body .archive-chip .chip-count { color: #8b93a7; font-size: 12px; font-weight: 500; } +.application-body .archive-table { background: #fff; border: 1px solid #eef0f5; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.04); } +.application-body .archive-table-head { display: grid; grid-template-columns: 56px minmax(0, 1fr) 180px 120px; align-items: center; min-height: 48px; padding: 0 20px; background: #fbfbfb; border-bottom: 1px solid #f3f3f3; color: #8b93a7; font-size: 13px; font-weight: 500; } +.application-body .archive-table-row { display: grid; grid-template-columns: 56px minmax(0, 1fr) 180px 120px; align-items: center; min-height: 58px; padding: 0 20px; border-bottom: 1px solid #f3f3f3; transition: background-color 0.12s ease; } +.application-body .archive-table-row:last-child { border-bottom: 0; } +.application-body .archive-table-row:hover { background: #fbfbfb; } +.application-body .archive-type-cell { display: flex; align-items: center; } +.application-body .archive-name-cell { display: flex; flex-direction: column; min-width: 0; } +.application-body .archive-item-title { color: #050841; font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.application-body .archive-item-type { color: #8b93a7; font-size: 12px; text-transform: capitalize; } +.application-body .archive-date-badge { color: #8b93a7; font-size: 13px; } +.application-body .archive-action-cell { display: flex; justify-content: flex-end; } +.application-body .archive-restore-btn { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px; background: #fff; border: 1px solid #dfe3ec; border-radius: 6px; color: #3d5df6; font-family: var(--meez-font-sans); font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; } +.application-body .archive-restore-btn:hover { background: #f1f5fe; border-color: #3d5df6; } +.application-body .empty-icon-circle { display: grid; place-items: center; width: 56px; height: 56px; margin-bottom: 16px; background: #f1f5fe; border-radius: 50%; color: #3d5df6; } + +/* Purchasing Review Tool */ +.application-body .purchasing-review-page { min-height: 100vh; background: #f3f3f3; padding-top: 48px; } +.application-body .purchasing-review-workspace { width: min(1120px, calc(100% - 48px)); margin: 0 auto; padding: 28px 0 64px; } +.application-body .purchasing-review-header { margin-bottom: 24px; } +.application-body .purchasing-breadcrumbs { margin-bottom: 8px; } +.application-body .purchasing-breadcrumbs a { display: inline-flex; align-items: center; gap: 4px; color: #3d5df6; font-size: 14px; font-weight: 500; text-decoration: none; } +.application-body .purchasing-review-header h1 { margin: 0 0 6px; color: #050841; font-size: 28px; font-weight: 700; letter-spacing: -0.015em; } +.application-body .purchasing-subtitle { color: #8b93a7; font-size: 14px; margin: 0; } +.application-body .purchasing-review-toolbar { position: sticky; top: 52px; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 20px; margin-bottom: 24px; background: #fff; border: 1px solid #eef0f5; border-radius: 8px; box-shadow: 0 2px 8px rgba(5,8,65,0.06); } +.application-body .purchasing-review-stats { display: flex; flex-direction: column; gap: 2px; } +.application-body .stats-counter { display: flex; align-items: center; gap: 6px; } +.application-body .stats-count { color: #3d5df6; font-size: 16px; font-weight: 700; } +.application-body .stats-label { color: #050841; font-size: 14px; font-weight: 500; } +.application-body .stats-hint { color: #8b93a7; font-size: 12px; } +.application-body .purchasing-review-actions { display: flex; align-items: center; gap: 12px; } +.application-body .purchasing-search-box { display: flex; align-items: center; gap: 6px; min-height: 38px; padding: 0 12px; background: #fbfbfb; border: 1px solid #dfe3ec; border-radius: 4px; } +.application-body .purchasing-search-box .search-icon { color: #8283a0; flex: none; } +.application-body .purchasing-search-box input { border: 0; background: transparent; outline: none; font-family: var(--meez-font-sans); font-size: 14px; color: #050841; min-width: 180px; } +.application-body .search-clear-btn { background: transparent; border: 0; color: #a5a9c1; font-size: 18px; cursor: pointer; padding: 0 2px; } +.application-body .purchasing-filter-toggle { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; } +.application-body .purchasing-filter-toggle input { position: absolute; opacity: 0; width: 0; height: 0; } +.application-body .toggle-track { display: inline-block; width: 34px; height: 18px; background: #cbd1dc; border-radius: 12px; position: relative; transition: background 0.15s ease; } +.application-body .toggle-thumb { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; background: #fff; border-radius: 50%; transition: transform 0.15s ease; } +.application-body .purchasing-filter-toggle input:checked + .toggle-track { background: #3d5df6; } +.application-body .purchasing-filter-toggle input:checked + .toggle-track .toggle-thumb { transform: translateX(16px); } +.application-body .toggle-label { color: #050841; font-size: 13px; font-weight: 500; } +.application-body .purchasing-export-btn { display: inline-flex; align-items: center; gap: 6px; min-height: 38px; padding: 0 16px; background: #3d5df6; color: #fff; border: 0; border-radius: 100px; font-family: var(--meez-font-sans); font-size: 14px; font-weight: 500; cursor: pointer; box-shadow: 0 2px 6px rgba(61, 93, 246, 0.28); transition: background 0.15s ease; } +.application-body .purchasing-export-btn:hover { background: #2b4be0; } +.application-body .purchasing-products-grid { display: flex; flex-direction: column; gap: 20px; } +.application-body .purchasing-card { padding: 24px; background: #fff; border: 1px solid #eef0f5; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); transition: border-color 0.15s ease; } +.application-body .purchasing-card.resolved { border-left: 3px solid #40b49a; } +.application-body .purchasing-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid #edf0f5; } +.application-body .purchasing-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; } +.application-body .supplier-badge { display: inline-flex; padding: 2px 8px; background: #eef2ff; border-radius: 4px; color: #3d5df6; font-size: 12px; font-weight: 600; text-transform: capitalize; } +.application-body .supplier-badge.walmart { background: #e6f1fc; color: #0071dc; } +.application-body .supplier-badge.sams_club { background: #e1f5fe; color: #0067a0; } +.application-body .sku-badge, .application-body .package-badge { display: inline-flex; padding: 2px 8px; background: #f7f7f8; border: 1px solid #ececec; border-radius: 4px; color: #687086; font-size: 12px; font-weight: 500; } +.application-body .price-badge { display: inline-flex; padding: 2px 8px; background: #e8f5e9; border-radius: 4px; color: #2e7d32; font-size: 12px; font-weight: 700; } +.application-body .purchasing-product-name { margin: 0; color: #050841; font-size: 18px; font-weight: 600; } +.application-body .purchasing-external-link { display: inline-flex; align-items: center; gap: 4px; color: #3d5df6; font-size: 13px; font-weight: 500; text-decoration: none; white-space: nowrap; } +.application-body .purchasing-external-link:hover { text-decoration: underline; } +.application-body .candidates-heading { display: block; margin-bottom: 12px; color: #687086; font-size: 13px; font-weight: 600; } +.application-body .candidate-options-list { display: flex; flex-direction: column; gap: 8px; } +.application-body .candidate-option-card { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 10px 16px; background: #fff; border: 1px solid #dfe3ec; border-radius: 6px; cursor: pointer; transition: all 0.12s ease; } +.application-body .candidate-option-card:hover { background: #f7f9fd; border-color: #bcc6e5; } +.application-body .candidate-option-card.selected { background: #f1f5fe; border-color: #3d5df6; box-shadow: 0 0 0 1px #3d5df6; } +.application-body .candidate-radio { display: none; } +.application-body .candidate-custom-radio { display: grid; place-items: center; width: 18px; height: 18px; border: 2px solid #a5a9c1; border-radius: 50%; flex: none; transition: all 0.12s ease; } +.application-body .candidate-custom-radio i { width: 8px; height: 8px; background: transparent; border-radius: 50%; transition: background 0.12s ease; } +.application-body .candidate-option-card.selected .candidate-custom-radio { border-color: #3d5df6; } +.application-body .candidate-option-card.selected .candidate-custom-radio i { background: #3d5df6; } +.application-body .candidate-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; } +.application-body .candidate-name { color: #050841; font-size: 15px; font-weight: 500; } +.application-body .candidate-meta { color: #8b93a7; font-size: 12px; } +.application-body .candidate-score-badge { display: inline-flex; padding: 2px 8px; background: #f1f5fe; border-radius: 12px; color: #3d5df6; font-size: 12px; font-weight: 600; } +.application-body .candidate-score-badge.high { background: #e8f5e9; color: #2e7d32; } +.application-body .candidate-option-card.none { border-style: dashed; } +.application-body .candidate-other-option { margin: 4px 0; padding: 6px 0; } +.application-body .other-select-label { display: flex; align-items: center; gap: 12px; } +.application-body .other-select-text { color: #687086; font-size: 13px; font-weight: 500; white-space: nowrap; } +.application-body .purchasing-select { flex: 1; min-height: 38px; padding: 6px 12px; background: #fff; border: 1px solid #dfe3ec; border-radius: 4px; color: #050841; font-family: var(--meez-font-sans); font-size: 14px; outline: none; cursor: pointer; } +.application-body .purchasing-select:focus { border-color: #3d5df6; } +.application-body .purchasing-notice-card { display: flex; align-items: flex-start; gap: 14px; padding: 24px; background: #fff; border: 1px solid #eef0f5; border-radius: 6px; color: #050841; } +.application-body .purchasing-notice-card svg { color: #3d5df6; flex: none; margin-top: 2px; } +.application-body .purchasing-notice-card strong { display: block; margin-bottom: 4px; font-size: 16px; } +.application-body .purchasing-notice-card p { margin: 0; color: #8b93a7; font-size: 14px; } .application-body .ingredient-merge { margin:24px 0; padding:16px; border:1px solid #f0d6d6; } .application-body .ingredient-merge summary { color:#a73838; cursor:pointer; font-weight:600; } .application-body .ingredient-merge form { display:flex; gap:8px; } @@ -4091,4 +4240,86 @@ input[type="search"]::-webkit-search-results-decoration, .bulk-dialog-close { top: 16px !important; right: 18px !important; } .bulk-ingredient-dialog footer { gap: 12px !important; } .bulk-ingredient-dialog .bulk-submit { min-width: 0 !important; flex: 1 !important; } + + /* Recipe Books Mobile */ + .application-body .recipe-book-workspace { + width: calc(100% - 32px) !important; + margin-inline: auto !important; + padding: 20px 0 40px !important; + } + .application-body .recipe-book-header { + flex-direction: column !important; + align-items: stretch !important; + gap: 16px !important; + margin-bottom: 20px !important; + } + .application-body .recipe-book-header-actions { + align-self: flex-start !important; + } + .application-body .book-recipe-row { + grid-template-columns: 32px minmax(0, 1fr) 28px !important; + padding: 0 14px !important; + min-height: 52px !important; + } + .application-body .book-directory-toolbar { + padding: 0 14px !important; + } + + /* Archive Mobile */ + .application-body .archive-workspace { + width: calc(100% - 32px) !important; + margin-inline: auto !important; + padding: 20px 0 40px !important; + } + .application-body .archive-table-head { + display: none !important; + } + .application-body .archive-table-row { + grid-template-columns: 36px minmax(0, 1fr) auto !important; + gap: 10px !important; + padding: 10px 14px !important; + min-height: 56px !important; + } + .application-body .archive-date-cell { + display: none !important; + } + + /* Purchasing Review Mobile */ + .application-body .purchasing-review-workspace { + width: calc(100% - 32px) !important; + margin-inline: auto !important; + padding: 20px 0 40px !important; + } + .application-body .purchasing-review-toolbar { + position: static !important; + flex-direction: column !important; + align-items: stretch !important; + gap: 14px !important; + padding: 14px !important; + } + .application-body .purchasing-review-actions { + flex-direction: column !important; + align-items: stretch !important; + gap: 10px !important; + } + .application-body .purchasing-search-box { + width: 100% !important; + } + .application-body .purchasing-search-box input { + min-width: 0 !important; + flex: 1 !important; + } + .application-body .purchasing-card { + padding: 16px !important; + } + .application-body .purchasing-card-header { + flex-direction: column !important; + align-items: flex-start !important; + gap: 10px !important; + } + .application-body .other-select-label { + flex-direction: column !important; + align-items: stretch !important; + gap: 6px !important; + } }