grocery-app/frontend/src/components/GroceryListItem.jsx
2026-01-02 14:27:39 -08:00

177 lines
4.7 KiB
JavaScript

import { useRef, useState } from "react";
import AddImageModal from "./AddImageModal";
import ConfirmBuyModal from "./ConfirmBuyModal";
import ImageModal from "./ImageModal";
export default function GroceryListItem({ item, onClick, onImageAdded, onLongPress }) {
const [showModal, setShowModal] = useState(false);
const [showAddImageModal, setShowAddImageModal] = useState(false);
const [showConfirmBuyModal, setShowConfirmBuyModal] = useState(false);
const longPressTimer = useRef(null);
const pressStartPos = useRef({ x: 0, y: 0 });
const handleTouchStart = (e) => {
const touch = e.touches[0];
pressStartPos.current = { x: touch.clientX, y: touch.clientY };
longPressTimer.current = setTimeout(() => {
if (onLongPress) {
onLongPress(item);
}
}, 500); // 500ms for long press
};
const handleTouchMove = (e) => {
// Cancel long press if finger moves too much
const touch = e.touches[0];
const moveDistance = Math.sqrt(
Math.pow(touch.clientX - pressStartPos.current.x, 2) +
Math.pow(touch.clientY - pressStartPos.current.y, 2)
);
if (moveDistance > 10) {
clearTimeout(longPressTimer.current);
}
};
const handleTouchEnd = () => {
clearTimeout(longPressTimer.current);
};
const handleMouseDown = () => {
longPressTimer.current = setTimeout(() => {
if (onLongPress) {
onLongPress(item);
}
}, 500);
};
const handleMouseUp = () => {
clearTimeout(longPressTimer.current);
};
const handleMouseLeave = () => {
clearTimeout(longPressTimer.current);
};
const handleItemClick = () => {
setShowConfirmBuyModal(true);
};
const handleConfirmBuy = (quantity) => {
if (onClick) {
onClick(quantity);
}
setShowConfirmBuyModal(false);
};
const handleCancelBuy = () => {
setShowConfirmBuyModal(false);
};
const handleImageClick = (e) => {
e.stopPropagation(); // Prevent triggering the bought action
if (item.item_image) {
setShowModal(true);
} else {
setShowAddImageModal(true);
}
};
const handleAddImage = async (imageFile) => {
if (onImageAdded) {
await onImageAdded(item.id, item.item_name, item.quantity, imageFile);
}
setShowAddImageModal(false);
};
const imageUrl = item.item_image && item.image_mime_type
? `data:${item.image_mime_type};base64,${item.item_image}`
: null;
const getTimeAgo = (dateString) => {
if (!dateString) return null;
const addedDate = new Date(dateString);
const now = new Date();
const diffMs = now - addedDate;
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays < 7) {
return `${diffDays}d ago`;
} else if (diffDays < 30) {
const weeks = Math.floor(diffDays / 7);
return `${weeks}w ago`;
} else {
const months = Math.floor(diffDays / 30);
return `${months}m ago`;
}
};
return (
<>
<li
className="glist-li"
onClick={handleItemClick}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
>
<div className="glist-item-layout">
<div
className={`glist-item-image ${item.item_image ? "has-image" : ""}`}
onClick={handleImageClick}
style={{ cursor: "pointer" }}
>
{item.item_image ? (
<img src={imageUrl} alt={item.item_name} />
) : (
<span>📦</span>
)}
<span className="glist-item-quantity">x{item.quantity}</span>
</div>
<div className="glist-item-content">
<div className="glist-item-header">
<span className="glist-item-name">{item.item_name}</span>
</div>
{item.added_by_users && item.added_by_users.length > 0 && (
<div className="glist-item-users">
{item.last_added_on && `${getTimeAgo(item.last_added_on)} -- `}
{item.added_by_users.join(" • ")}
</div>
)}
</div>
</div>
</li>
{showModal && (
<ImageModal
imageUrl={imageUrl}
itemName={item.item_name}
onClose={() => setShowModal(false)}
/>
)}
{showAddImageModal && (
<AddImageModal
itemName={item.item_name}
onClose={() => setShowAddImageModal(false)}
onAddImage={handleAddImage}
/>
)}
{showConfirmBuyModal && (
<ConfirmBuyModal
item={item}
onConfirm={handleConfirmBuy}
onCancel={handleCancelBuy}
/>
)}
</>
);
}