From 1300cbb0a800b500b5e0ecbcc9f23f53c97b9dad Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 22 Jan 2026 00:41:46 -0800 Subject: [PATCH] merge image and confirmbuy modal and improve on them --- .../src/components/items/GroceryListItem.jsx | 32 +-- .../src/components/modals/ConfirmBuyModal.jsx | 66 +++++- .../src/components/modals/EditItemModal.jsx | 37 +++- frontend/src/pages/GroceryList.jsx | 12 +- frontend/src/styles/ConfirmBuyModal.css | 202 +++++++++++++++--- .../src/styles/components/EditItemModal.css | 23 ++ 6 files changed, 320 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/items/GroceryListItem.jsx b/frontend/src/components/items/GroceryListItem.jsx index 0c506b7..9e9d3d4 100644 --- a/frontend/src/components/items/GroceryListItem.jsx +++ b/frontend/src/components/items/GroceryListItem.jsx @@ -1,12 +1,11 @@ import { memo, useRef, useState } from "react"; import AddImageModal from "../modals/AddImageModal"; import ConfirmBuyModal from "../modals/ConfirmBuyModal"; -import ImageModal from "../modals/ImageModal"; -function GroceryListItem({ item, onClick, onImageAdded, onLongPress }) { - const [showModal, setShowModal] = useState(false); +function GroceryListItem({ item, onClick, onImageAdded, onLongPress, allItems = [] }) { const [showAddImageModal, setShowAddImageModal] = useState(false); const [showConfirmBuyModal, setShowConfirmBuyModal] = useState(false); + const [currentItem, setCurrentItem] = useState(item); const longPressTimer = useRef(null); const pressStartPos = useRef({ x: 0, y: 0 }); @@ -57,13 +56,14 @@ function GroceryListItem({ item, onClick, onImageAdded, onLongPress }) { const handleItemClick = () => { if (onClick) { + setCurrentItem(item); setShowConfirmBuyModal(true); } }; const handleConfirmBuy = (quantity) => { if (onClick) { - onClick(quantity); + onClick(currentItem.id, quantity); } setShowConfirmBuyModal(false); }; @@ -72,10 +72,16 @@ function GroceryListItem({ item, onClick, onImageAdded, onLongPress }) { setShowConfirmBuyModal(false); }; + const handleNavigate = (newItem) => { + setCurrentItem(newItem); + }; + const handleImageClick = (e) => { e.stopPropagation(); // Prevent triggering the bought action if (item.item_image) { - setShowModal(true); + // Open buy modal which now shows the image + setCurrentItem(item); + setShowConfirmBuyModal(true); } else { setShowAddImageModal(true); } @@ -150,14 +156,6 @@ function GroceryListItem({ item, onClick, onImageAdded, onLongPress }) { - {showModal && ( - setShowModal(false)} - /> - )} - {showAddImageModal && ( )} @@ -187,9 +187,11 @@ export default memo(GroceryListItem, (prevProps, nextProps) => { prevProps.item.item_image === nextProps.item.item_image && prevProps.item.bought === nextProps.item.bought && prevProps.item.last_added_on === nextProps.item.last_added_on && + prevProps.item.zone === nextProps.item.zone && prevProps.item.added_by_users?.join(',') === nextProps.item.added_by_users?.join(',') && prevProps.onClick === nextProps.onClick && prevProps.onImageAdded === nextProps.onImageAdded && - prevProps.onLongPress === nextProps.onLongPress + prevProps.onLongPress === nextProps.onLongPress && + prevProps.allItems?.length === nextProps.allItems?.length ); }); diff --git a/frontend/src/components/modals/ConfirmBuyModal.jsx b/frontend/src/components/modals/ConfirmBuyModal.jsx index e3a0dea..a8b888c 100644 --- a/frontend/src/components/modals/ConfirmBuyModal.jsx +++ b/frontend/src/components/modals/ConfirmBuyModal.jsx @@ -1,10 +1,21 @@ import { useState } from "react"; import "../../styles/ConfirmBuyModal.css"; -export default function ConfirmBuyModal({ item, onConfirm, onCancel }) { +export default function ConfirmBuyModal({ + item, + onConfirm, + onCancel, + allItems = [], + onNavigate +}) { const [quantity, setQuantity] = useState(item.quantity); const maxQuantity = item.quantity; + // Find current index and check for prev/next + const currentIndex = allItems.findIndex(i => i.id === item.id); + const hasPrev = currentIndex > 0; + const hasNext = currentIndex < allItems.length - 1; + const handleIncrement = () => { if (quantity < maxQuantity) { setQuantity(prev => prev + 1); @@ -21,14 +32,61 @@ export default function ConfirmBuyModal({ item, onConfirm, onCancel }) { onConfirm(quantity); }; + const handlePrev = () => { + if (hasPrev && onNavigate) { + const prevItem = allItems[currentIndex - 1]; + onNavigate(prevItem); + } + }; + + const handleNext = () => { + if (hasNext && onNavigate) { + const nextItem = allItems[currentIndex + 1]; + onNavigate(nextItem); + } + }; + + const imageUrl = item.item_image && item.image_mime_type + ? `data:${item.image_mime_type};base64,${item.item_image}` + : null; + return (
e.stopPropagation()}> -

Mark as Bought

-

"{item.item_name}"

+
+ {item.zone &&
{item.zone}
} +

{item.item_name}

+
+ +
+ + +
+ {imageUrl ? ( + {item.item_name} + ) : ( +
📦
+ )} +
+ + +
-

Quantity to buy:

+
+
+ + {showImageModal && ( + setShowImageModal(false)} + onAddImage={handleImageUpload} + /> + )}
); } diff --git a/frontend/src/pages/GroceryList.jsx b/frontend/src/pages/GroceryList.jsx index c7b1975..ec1f42e 100644 --- a/frontend/src/pages/GroceryList.jsx +++ b/frontend/src/pages/GroceryList.jsx @@ -377,8 +377,9 @@ export default function GroceryList() { - [ROLES.ADMIN, ROLES.EDITOR].includes(role) && handleBought(item.id, quantity) + allItems={sortedItems} + onClick={(id, quantity) => + [ROLES.ADMIN, ROLES.EDITOR].includes(role) && handleBought(id, quantity) } onImageAdded={ [ROLES.ADMIN, ROLES.EDITOR].includes(role) ? handleImageAdded : null @@ -398,8 +399,9 @@ export default function GroceryList() { - [ROLES.ADMIN, ROLES.EDITOR].includes(role) && handleBought(item.id, quantity) + allItems={sortedItems} + onClick={(id, quantity) => + [ROLES.ADMIN, ROLES.EDITOR].includes(role) && handleBought(id, quantity) } onImageAdded={ [ROLES.ADMIN, ROLES.EDITOR].includes(role) ? handleImageAdded : null @@ -420,6 +422,7 @@ export default function GroceryList() { )}
diff --git a/frontend/src/styles/ConfirmBuyModal.css b/frontend/src/styles/ConfirmBuyModal.css index 2a4adc9..ab1dcd4 100644 --- a/frontend/src/styles/ConfirmBuyModal.css +++ b/frontend/src/styles/ConfirmBuyModal.css @@ -14,7 +14,7 @@ .confirm-buy-modal { background: white; - padding: 2em; + padding: 1em; border-radius: 12px; max-width: 450px; width: 90%; @@ -22,47 +22,107 @@ animation: slideUp 0.3s ease-out; } -.confirm-buy-modal h2 { - margin: 0 0 0.5em 0; - font-size: 1.5em; - color: #333; +.confirm-buy-header { text-align: center; + margin-bottom: 0.5em; +} + +.confirm-buy-zone { + font-size: 0.85em; + color: #666; + font-weight: 500; + margin-bottom: 0.2em; + text-transform: uppercase; + letter-spacing: 0.5px; } .confirm-buy-item-name { - margin: 0 0 1.5em 0; - font-size: 1.1em; + margin: 0; + font-size: 1.2em; color: #007bff; font-weight: 600; - text-align: center; +} + +.confirm-buy-image-section { + display: flex; + align-items: center; + justify-content: center; + gap: 0.6em; + margin: 0.8em 0; +} + +.confirm-buy-nav-btn { + width: 35px; + height: 35px; + border: 2px solid #007bff; + border-radius: 50%; + background: white; + color: #007bff; + font-size: 1.8em; + font-weight: bold; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + padding: 0; + flex-shrink: 0; +} + +.confirm-buy-nav-btn:hover:not(:disabled) { + background: #007bff; + color: white; +} + +.confirm-buy-nav-btn:disabled { + border-color: #ccc; + color: #ccc; + cursor: not-allowed; +} + +.confirm-buy-image-container { + width: 280px; + height: 280px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid #ddd; + border-radius: 8px; + overflow: hidden; + background: #f8f9fa; +} + +.confirm-buy-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.confirm-buy-image-placeholder { + font-size: 4em; + color: #ccc; } .confirm-buy-quantity-section { - margin: 2em 0; -} - -.confirm-buy-label { - margin: 0 0 1em 0; - font-size: 1em; - color: #555; - text-align: center; + margin: 0.8em 0; } .confirm-buy-counter { display: flex; align-items: center; justify-content: center; - gap: 1em; + gap: 0.8em; } .confirm-buy-counter-btn { - width: 50px; - height: 50px; + width: 45px; + height: 45px; border: 2px solid #007bff; border-radius: 8px; background: white; color: #007bff; - font-size: 1.8em; + font-size: 1.6em; font-weight: bold; cursor: pointer; transition: all 0.2s; @@ -85,12 +145,12 @@ } .confirm-buy-counter-display { - width: 80px; - height: 50px; + width: 70px; + height: 45px; border: 2px solid #ddd; border-radius: 8px; text-align: center; - font-size: 1.5em; + font-size: 1.4em; font-weight: bold; color: #333; background: #f8f9fa; @@ -103,20 +163,21 @@ .confirm-buy-actions { display: flex; - gap: 1em; - margin-top: 2em; + gap: 0.6em; + margin-top: 1em; } .confirm-buy-cancel, .confirm-buy-confirm { flex: 1; - padding: 0.9em; + padding: 0.75em 0.5em; border: none; border-radius: 8px; - font-size: 1em; - font-weight: 500; + font-size: 0.95em; + font-weight: 600; cursor: pointer; transition: all 0.2s; + white-space: nowrap; } .confirm-buy-cancel { @@ -156,3 +217,88 @@ opacity: 1; } } + +/* Mobile optimizations */ +@media (max-width: 480px) { + .confirm-buy-modal { + padding: 0.8em; + } + + .confirm-buy-header { + margin-bottom: 0.4em; + } + + .confirm-buy-zone { + font-size: 0.8em; + } + + .confirm-buy-item-name { + font-size: 1.1em; + } + + .confirm-buy-image-section { + gap: 0.5em; + margin: 0.6em 0; + } + + .confirm-buy-actions { + gap: 0.5em; + margin-top: 0.8em; + } + + .confirm-buy-cancel, + .confirm-buy-confirm { + padding: 0.7em 0.4em; + font-size: 0.9em; + } + + .confirm-buy-image-container { + width: 220px; + height: 220px; + } + + .confirm-buy-nav-btn { + width: 30px; + height: 30px; + font-size: 1.6em; + } + + .confirm-buy-counter-btn { + width: 40px; + height: 40px; + font-size: 1.4em; + } + + .confirm-buy-counter-display { + width: 60px; + height: 40px; + font-size: 1.2em; + } + + .confirm-buy-quantity-section { + margin: 0.6em 0; + } +} + +@media (max-width: 360px) { + .confirm-buy-modal { + padding: 0.7em; + } + + .confirm-buy-cancel, + .confirm-buy-confirm { + padding: 0.65em 0.3em; + font-size: 0.85em; + } + + .confirm-buy-image-container { + width: 180px; + height: 180px; + } + + .confirm-buy-nav-btn { + width: 28px; + height: 28px; + font-size: 1.4em; + } +} diff --git a/frontend/src/styles/components/EditItemModal.css b/frontend/src/styles/components/EditItemModal.css index 1f1e2d4..ab86854 100644 --- a/frontend/src/styles/components/EditItemModal.css +++ b/frontend/src/styles/components/EditItemModal.css @@ -110,3 +110,26 @@ .edit-modal-btn-save:hover:not(:disabled) { background: #0056b3; } + +.edit-modal-btn-image { + width: 100%; + padding: 0.7em; + font-size: 1em; + border: 2px solid #28a745; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + transition: all 0.2s; + background: white; + color: #28a745; +} + +.edit-modal-btn-image:hover:not(:disabled) { + background: #28a745; + color: white; +} + +.edit-modal-btn-image:disabled { + opacity: 0.6; + cursor: not-allowed; +}