fix add item formatting and suggestion loigic
This commit is contained in:
parent
aa9e71194c
commit
0d5316bc27
@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import "../../styles/components/AddItemForm.css";
|
||||
import SuggestionList from "../items/SuggestionList";
|
||||
|
||||
export default function AddItemForm({ onAdd, onSuggest, suggestions, buttonText = "Add Item" }) {
|
||||
export default function AddItemForm({ onAdd, onSuggest, suggestions, buttonText = "Add" }) {
|
||||
const [itemName, setItemName] = useState("");
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
@ -23,38 +24,76 @@ export default function AddItemForm({ onAdd, onSuggest, suggestions, buttonText
|
||||
const handleSuggestionSelect = (suggestion) => {
|
||||
setItemName(suggestion);
|
||||
setShowSuggestions(false);
|
||||
onSuggest(suggestion); // Trigger button text update
|
||||
};
|
||||
|
||||
const incrementQuantity = () => {
|
||||
setQuantity(prev => prev + 1);
|
||||
};
|
||||
|
||||
const decrementQuantity = () => {
|
||||
setQuantity(prev => Math.max(1, prev - 1));
|
||||
};
|
||||
|
||||
const isDisabled = !itemName.trim();
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
className="glist-input"
|
||||
placeholder="Item name"
|
||||
value={itemName}
|
||||
onChange={(e) => handleInputChange(e.target.value)}
|
||||
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
|
||||
onClick={() => setShowSuggestions(true)}
|
||||
/>
|
||||
<div className="add-item-form-container">
|
||||
<form onSubmit={handleSubmit} className="add-item-form">
|
||||
<div className="add-item-form-field">
|
||||
<input
|
||||
type="text"
|
||||
className="add-item-form-input"
|
||||
placeholder="Enter item name"
|
||||
value={itemName}
|
||||
onChange={(e) => handleInputChange(e.target.value)}
|
||||
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
|
||||
onClick={() => setShowSuggestions(true)}
|
||||
/>
|
||||
|
||||
{showSuggestions && suggestions.length > 0 && (
|
||||
<SuggestionList
|
||||
suggestions={suggestions}
|
||||
onSelect={handleSuggestionSelect}
|
||||
/>
|
||||
)}
|
||||
{showSuggestions && suggestions.length > 0 && (
|
||||
<SuggestionList
|
||||
suggestions={suggestions}
|
||||
onSelect={handleSuggestionSelect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
className="glist-input"
|
||||
value={quantity}
|
||||
onChange={(e) => setQuantity(Number(e.target.value))}
|
||||
/>
|
||||
<div className="add-item-form-actions">
|
||||
<div className="add-item-form-quantity-control">
|
||||
<button
|
||||
type="button"
|
||||
className="quantity-btn quantity-btn-minus"
|
||||
onClick={decrementQuantity}
|
||||
disabled={quantity <= 1}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
className="add-item-form-quantity-input"
|
||||
value={quantity}
|
||||
readOnly
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="quantity-btn quantity-btn-plus"
|
||||
onClick={incrementQuantity}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="glist-btn">
|
||||
{buttonText}
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
type="submit"
|
||||
className={`add-item-form-submit ${isDisabled ? 'disabled' : ''}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{buttonText}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
suggestions: string[];
|
||||
onSelect: (value: string) => void;
|
||||
@ -8,15 +10,12 @@ export default function SuggestionList({ suggestions, onSelect }: Props) {
|
||||
|
||||
return (
|
||||
<ul
|
||||
className="suggestion-list"
|
||||
style={{
|
||||
background: "#fff",
|
||||
border: "1px solid #ccc",
|
||||
maxHeight: "150px",
|
||||
overflowY: "auto",
|
||||
position: "absolute",
|
||||
zIndex: 1000,
|
||||
left: "1em",
|
||||
right: "1em",
|
||||
listStyle: "none",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
|
||||
@ -21,7 +21,7 @@ export default function GroceryList() {
|
||||
const [sortedItems, setSortedItems] = useState([]);
|
||||
const [sortMode, setSortMode] = useState("zone");
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [showAddForm, setShowAddForm] = useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [buttonText, setButtonText] = useState("Add Item");
|
||||
const [pendingItem, setPendingItem] = useState(null);
|
||||
@ -103,16 +103,9 @@ export default function GroceryList() {
|
||||
const exactMatch = allItems.find(item => item.item_name.toLowerCase() === lowerText);
|
||||
|
||||
if (exactMatch) {
|
||||
setButtonText("Add Item");
|
||||
setButtonText("Add");
|
||||
} else {
|
||||
// Check for similar items (80% match)
|
||||
const similar = findSimilarItems(text, allItems, 80);
|
||||
if (similar.length > 0) {
|
||||
// Show suggestion in button but allow creation
|
||||
setButtonText("Create and Add Item");
|
||||
} else {
|
||||
setButtonText("Create and Add Item");
|
||||
}
|
||||
setButtonText("Create + Add");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -129,10 +122,23 @@ export default function GroceryList() {
|
||||
|
||||
const lowerItemName = itemName.toLowerCase().trim();
|
||||
|
||||
// Combine both unbought and recently bought items for similarity checking
|
||||
const allItems = [...items, ...recentlyBoughtItems];
|
||||
// First check if exact item exists in database (case-insensitive)
|
||||
let existingItem = null;
|
||||
try {
|
||||
const response = await getItemByName(itemName);
|
||||
existingItem = response.data;
|
||||
} catch {
|
||||
existingItem = null;
|
||||
}
|
||||
|
||||
// Check for 80% similar items
|
||||
// If exact item exists, skip similarity check and process directly
|
||||
if (existingItem) {
|
||||
await processItemAddition(itemName, quantity);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only check for similar items if exact item doesn't exist
|
||||
const allItems = [...items, ...recentlyBoughtItems];
|
||||
const similar = findSimilarItems(itemName, allItems, 80);
|
||||
if (similar.length > 0) {
|
||||
// Show modal and wait for user decision
|
||||
@ -141,7 +147,7 @@ export default function GroceryList() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue with normal flow
|
||||
// Continue with normal flow for new items
|
||||
await processItemAddition(itemName, quantity);
|
||||
};
|
||||
|
||||
@ -327,7 +333,6 @@ export default function GroceryList() {
|
||||
<div className="glist-container">
|
||||
<h1 className="glist-title">Costco Grocery List</h1>
|
||||
|
||||
<SortDropdown value={sortMode} onChange={setSortMode} />
|
||||
|
||||
{[ROLES.ADMIN, ROLES.EDITOR].includes(role) && showAddForm && (
|
||||
<AddItemForm
|
||||
@ -338,6 +343,8 @@ export default function GroceryList() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<SortDropdown value={sortMode} onChange={setSortMode} />
|
||||
|
||||
{sortMode === "zone" ? (
|
||||
// Grouped view by zone
|
||||
(() => {
|
||||
|
||||
172
frontend/src/styles/components/AddItemForm.css
Normal file
172
frontend/src/styles/components/AddItemForm.css
Normal file
@ -0,0 +1,172 @@
|
||||
/* Add Item Form Container */
|
||||
.add-item-form-container {
|
||||
background: var(--color-bg-surface);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
border: var(--border-width-thin) solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.add-item-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Form Fields */
|
||||
.add-item-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.add-item-form-input {
|
||||
padding: var(--input-padding-y) var(--input-padding-x);
|
||||
border: var(--border-width-thin) solid var(--input-border-color);
|
||||
border-radius: var(--input-border-radius);
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-family-base);
|
||||
transition: var(--transition-base);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.add-item-form-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--input-focus-border-color);
|
||||
box-shadow: var(--input-focus-shadow);
|
||||
}
|
||||
|
||||
/* Suggestion List Positioning */
|
||||
.add-item-form-field .suggestion-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: var(--spacing-xs);
|
||||
z-index: var(--z-dropdown);
|
||||
}
|
||||
|
||||
/* Actions Row */
|
||||
.add-item-form-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Quantity Control */
|
||||
.add-item-form-quantity-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: var(--border-width-thin) solid var(--color-border-medium);
|
||||
background: var(--color-bg-surface);
|
||||
color: var(--color-text-primary);
|
||||
border-radius: var(--border-radius-sm);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.quantity-btn:hover:not(:disabled) {
|
||||
background: var(--color-primary-light);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.quantity-btn:active:not(:disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.quantity-btn:disabled {
|
||||
background: var(--color-bg-disabled);
|
||||
color: var(--color-text-disabled);
|
||||
border-color: var(--color-border-disabled);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.add-item-form-quantity-input {
|
||||
width: 40px;
|
||||
max-width: 40px;
|
||||
padding: var(--input-padding-y) var(--input-padding-x);
|
||||
border: var(--border-width-thin) solid var(--input-border-color);
|
||||
border-radius: var(--input-border-radius);
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-family-base);
|
||||
text-align: center;
|
||||
transition: var(--transition-base);
|
||||
-moz-appearance: textfield; /* Remove spinner in Firefox */
|
||||
}
|
||||
|
||||
/* Remove spinner arrows in Chrome/Safari */
|
||||
.add-item-form-quantity-input::-webkit-outer-spin-button,
|
||||
.add-item-form-quantity-input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.add-item-form-quantity-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--input-focus-border-color);
|
||||
box-shadow: var(--input-focus-shadow);
|
||||
}
|
||||
|
||||
/* Submit Button */
|
||||
.add-item-form-submit {
|
||||
height: 40px;
|
||||
padding: 0 var(--spacing-lg);
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
border: none;
|
||||
border-radius: var(--button-border-radius);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--button-font-weight);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-base);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.add-item-form-submit:hover:not(:disabled) {
|
||||
background: var(--color-primary-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.add-item-form-submit:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.add-item-form-submit.disabled,
|
||||
.add-item-form-submit:disabled {
|
||||
background: var(--color-bg-disabled);
|
||||
color: var(--color-text-disabled);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.add-item-form-container {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/* Container */
|
||||
.glist-body {
|
||||
font-family: var(--font-family-base);
|
||||
padding: var(--spacing-md);
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-bg-body);
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
max-width: var(--container-max-width);
|
||||
margin: auto;
|
||||
background: var(--color-bg-surface);
|
||||
padding: var(--spacing-md);
|
||||
padding: var(--spacing-sm);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@
|
||||
--color-text-secondary: #6c757d;
|
||||
--color-text-muted: #adb5bd;
|
||||
--color-text-inverse: #ffffff;
|
||||
--color-text-disabled: #6c757d;
|
||||
|
||||
/* Background Colors */
|
||||
--color-bg-body: #f8f9fa;
|
||||
@ -71,6 +72,7 @@
|
||||
--color-border-light: #e0e0e0;
|
||||
--color-border-medium: #ccc;
|
||||
--color-border-dark: #999;
|
||||
--color-border-disabled: #dee2e6;
|
||||
|
||||
/* ============================================
|
||||
SPACING
|
||||
|
||||
Loading…
Reference in New Issue
Block a user