cleaned up grocerylist page, created reference document for cleaning up files, and consolidated all readme docs within docs folder
This commit is contained in:
parent
bc7e212eea
commit
0c16d22c1e
473
docs/code-cleanup-guide.md
Normal file
473
docs/code-cleanup-guide.md
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
# Code Cleanup Guide
|
||||||
|
|
||||||
|
This guide documents the cleanup patterns and best practices applied to the codebase, starting with `GroceryList.jsx`. Use this as a reference for maintaining consistent, clean, and readable code across all files.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
1. [Spacing & Organization](#spacing--organization)
|
||||||
|
2. [Comment Formatting](#comment-formatting)
|
||||||
|
3. [Code Simplification](#code-simplification)
|
||||||
|
4. [React Performance Patterns](#react-performance-patterns)
|
||||||
|
5. [Cleanup Checklist](#cleanup-checklist)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spacing & Organization
|
||||||
|
|
||||||
|
### Two-Line Separation
|
||||||
|
Use **2 blank lines** to separate logical groups and functions.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
const handleAdd = async (itemName, quantity) => {
|
||||||
|
// function body
|
||||||
|
};
|
||||||
|
const handleBought = async (id) => {
|
||||||
|
// function body
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
const handleAdd = async (itemName, quantity) => {
|
||||||
|
// function body
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleBought = async (id) => {
|
||||||
|
// function body
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical Grouping
|
||||||
|
Organize code into clear sections:
|
||||||
|
- State declarations
|
||||||
|
- Data loading functions
|
||||||
|
- Computed values (useMemo)
|
||||||
|
- Event handlers grouped by functionality
|
||||||
|
- Helper functions
|
||||||
|
- Render logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comment Formatting
|
||||||
|
|
||||||
|
### Section Headers
|
||||||
|
Use the `=== Section Name ===` format for major sections, followed by 2 blank lines before the next code block.
|
||||||
|
|
||||||
|
**Pattern:**
|
||||||
|
```javascript
|
||||||
|
// === State ===
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
|
||||||
|
// === Data Loading ===
|
||||||
|
const loadItems = async () => {
|
||||||
|
// implementation
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// === Event Handlers ===
|
||||||
|
const handleClick = () => {
|
||||||
|
// implementation
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Section Names
|
||||||
|
- `=== State ===`
|
||||||
|
- `=== Data Loading ===`
|
||||||
|
- `=== Computed Values ===` or `=== Sorted Items Computation ===`
|
||||||
|
- `=== Event Handlers ===` or specific groups like `=== Item Addition Handlers ===`
|
||||||
|
- `=== Helper Functions ===`
|
||||||
|
- `=== Render ===`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Simplification
|
||||||
|
|
||||||
|
### 1. Optional Chaining
|
||||||
|
Replace `&&` null checks with optional chaining when accessing nested properties.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
if (existingItem && existingItem.bought === false) {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
if (existingItem?.bought === false) {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Accessing properties on potentially undefined/null objects
|
||||||
|
- Checking nested properties: `user?.profile?.name`
|
||||||
|
- Method calls: `item?.toString?.()`
|
||||||
|
|
||||||
|
**When NOT to use:**
|
||||||
|
- When you need to check if the object exists first (use explicit check)
|
||||||
|
- For boolean coercion: `if (item)` is clearer than `if (item?.)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Ternary Operators
|
||||||
|
Use ternary operators for simple conditional assignments and returns.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
let result;
|
||||||
|
if (condition) {
|
||||||
|
result = "yes";
|
||||||
|
} else {
|
||||||
|
result = "no";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
const result = condition ? "yes" : "no";
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Simple conditional assignments
|
||||||
|
- Inline JSX conditionals
|
||||||
|
- Return statements with simple conditions
|
||||||
|
|
||||||
|
**When NOT to use:**
|
||||||
|
- Complex multi-line logic (use if/else for readability)
|
||||||
|
- Nested ternaries (hard to read)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Early Returns
|
||||||
|
Use early returns to reduce nesting.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
const handleSuggest = async (text) => {
|
||||||
|
if (text.trim()) {
|
||||||
|
// long implementation
|
||||||
|
} else {
|
||||||
|
setSuggestions([]);
|
||||||
|
setButtonText("Add Item");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
const handleSuggest = async (text) => {
|
||||||
|
if (!text.trim()) {
|
||||||
|
setSuggestions([]);
|
||||||
|
setButtonText("Add Item");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// main implementation without nesting
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Destructuring
|
||||||
|
Use destructuring for cleaner variable access.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
const username = user.username;
|
||||||
|
const email = user.email;
|
||||||
|
const role = user.role;
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
const { username, email, role } = user;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Array Methods Over Loops
|
||||||
|
Prefer array methods (`.map()`, `.filter()`, `.find()`) over traditional loops.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
const activeItems = [];
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (!items[i].bought) {
|
||||||
|
activeItems.push(items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
const activeItems = items.filter(item => !item.bought);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## React Performance Patterns
|
||||||
|
|
||||||
|
### 1. useCallback for Event Handlers
|
||||||
|
Wrap event handlers in `useCallback` to prevent unnecessary re-renders of child components.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const handleBought = useCallback(async (id, quantity) => {
|
||||||
|
await markBought(id);
|
||||||
|
setItems(prevItems => prevItems.filter(item => item.id !== id));
|
||||||
|
loadRecentlyBought();
|
||||||
|
}, []); // Add dependencies if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Handler functions passed as props to memoized child components
|
||||||
|
- Functions used as dependencies in other hooks
|
||||||
|
- Functions in frequently re-rendering components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. useMemo for Expensive Computations
|
||||||
|
Use `useMemo` for computationally expensive operations or large transformations.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const sortedItems = useMemo(() => {
|
||||||
|
const sorted = [...items];
|
||||||
|
|
||||||
|
if (sortMode === "az") {
|
||||||
|
sorted.sort((a, b) => a.item_name.localeCompare(b.item_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}, [items, sortMode]);
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Sorting/filtering large arrays
|
||||||
|
- Complex calculations
|
||||||
|
- Derived state that's expensive to compute
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. React.memo for Components
|
||||||
|
Wrap components with `React.memo` and provide custom comparison functions to prevent unnecessary re-renders.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const GroceryListItem = React.memo(
|
||||||
|
({ item, onClick, onLongPress }) => {
|
||||||
|
// component implementation
|
||||||
|
},
|
||||||
|
(prevProps, nextProps) => {
|
||||||
|
return (
|
||||||
|
prevProps.id === nextProps.id &&
|
||||||
|
prevProps.item_name === nextProps.item_name &&
|
||||||
|
prevProps.quantity === nextProps.quantity &&
|
||||||
|
prevProps.item_image === nextProps.item_image
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- List item components that render frequently
|
||||||
|
- Components with stable props
|
||||||
|
- Pure components (output depends only on props)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. In-Place State Updates
|
||||||
|
Update specific items in state instead of reloading entire datasets.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
const handleUpdate = async (id, newData) => {
|
||||||
|
await updateItem(id, newData);
|
||||||
|
loadItems(); // Reloads entire list from server
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
const handleUpdate = useCallback(async (id, newData) => {
|
||||||
|
const response = await updateItem(id, newData);
|
||||||
|
|
||||||
|
setItems(prevItems =>
|
||||||
|
prevItems.map(item =>
|
||||||
|
item.id === id ? { ...item, ...response.data } : item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Faster updates (no server round-trip for entire list)
|
||||||
|
- Preserves scroll position
|
||||||
|
- Better user experience (no full re-render)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cleanup Checklist
|
||||||
|
|
||||||
|
Use this checklist when cleaning up a file:
|
||||||
|
|
||||||
|
### Structure & Organization
|
||||||
|
- [ ] Group related state variables together
|
||||||
|
- [ ] Use 2-line spacing between logical sections
|
||||||
|
- [ ] Add section comments using `=== Format ===`
|
||||||
|
- [ ] Order sections logically (state → data loading → computed → handlers → helpers → render)
|
||||||
|
|
||||||
|
### Code Simplification
|
||||||
|
- [ ] Replace `&&` null checks with optional chaining where appropriate
|
||||||
|
- [ ] Convert simple if/else to ternary operators
|
||||||
|
- [ ] Use early returns to reduce nesting
|
||||||
|
- [ ] Apply destructuring for cleaner variable access
|
||||||
|
- [ ] Use array methods instead of loops
|
||||||
|
|
||||||
|
### React Performance
|
||||||
|
- [ ] Wrap stable event handlers in `useCallback`
|
||||||
|
- [ ] Use `useMemo` for expensive computations
|
||||||
|
- [ ] Consider `React.memo` for list items or frequently re-rendering components
|
||||||
|
- [ ] Update state in-place instead of reloading from server
|
||||||
|
|
||||||
|
### Consistency
|
||||||
|
- [ ] Check naming conventions (camelCase for functions/variables)
|
||||||
|
- [ ] Ensure consistent spacing and indentation
|
||||||
|
- [ ] Remove unused imports and variables
|
||||||
|
- [ ] Remove console.logs (except intentional debugging aids)
|
||||||
|
|
||||||
|
### Testing After Cleanup
|
||||||
|
- [ ] Verify no functionality broke
|
||||||
|
- [ ] Check that performance improved (using React DevTools Profiler)
|
||||||
|
- [ ] Test all interactive features
|
||||||
|
- [ ] Verify mobile/responsive behavior still works
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Before & After
|
||||||
|
|
||||||
|
### Before Cleanup
|
||||||
|
```javascript
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export default function MyComponent() {
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const loadItems = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await getItems();
|
||||||
|
setItems(res.data);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
loadItems();
|
||||||
|
}, []);
|
||||||
|
const handleUpdate = async (id, data) => {
|
||||||
|
await updateItem(id, data);
|
||||||
|
loadItems();
|
||||||
|
};
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
await deleteItem(id);
|
||||||
|
loadItems();
|
||||||
|
};
|
||||||
|
if (loading) return <p>Loading...</p>;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{items.map(item => (
|
||||||
|
<Item key={item.id} item={item} onUpdate={handleUpdate} onDelete={handleDelete} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Cleanup
|
||||||
|
```javascript
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export default function MyComponent() {
|
||||||
|
// === State ===
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
|
||||||
|
// === Data Loading ===
|
||||||
|
const loadItems = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await getItems();
|
||||||
|
setItems(res.data);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadItems();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// === Event Handlers ===
|
||||||
|
const handleUpdate = useCallback(async (id, data) => {
|
||||||
|
const response = await updateItem(id, data);
|
||||||
|
|
||||||
|
setItems(prevItems =>
|
||||||
|
prevItems.map(item =>
|
||||||
|
item.id === id ? { ...item, ...response.data } : item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const handleDelete = useCallback(async (id) => {
|
||||||
|
await deleteItem(id);
|
||||||
|
setItems(prevItems => prevItems.filter(item => item.id !== id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// === Render ===
|
||||||
|
if (loading) return <p>Loading...</p>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{items.map(item => (
|
||||||
|
<Item
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
onUpdate={handleUpdate}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key improvements:**
|
||||||
|
1. Added section comments for clarity
|
||||||
|
2. Proper 2-line spacing between sections
|
||||||
|
3. Wrapped handlers in `useCallback` for performance
|
||||||
|
4. In-place state updates instead of reloading entire list
|
||||||
|
5. Better import organization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [React Performance Optimization](https://react.dev/learn/render-and-commit)
|
||||||
|
- [useCallback Hook](https://react.dev/reference/react/useCallback)
|
||||||
|
- [useMemo Hook](https://react.dev/reference/react/useMemo)
|
||||||
|
- [React.memo](https://react.dev/reference/react/memo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Don't over-optimize**: Not every component needs `useCallback`/`useMemo`. Apply these patterns when you have measurable performance issues or when working with large lists.
|
||||||
|
- **Readability first**: If a simplification makes code harder to understand, skip it. Code should be optimized for human reading first.
|
||||||
|
- **Test thoroughly**: Always test after cleanup to ensure no functionality broke.
|
||||||
|
- **Incremental cleanup**: Don't try to clean up everything at once. Focus on one file at a time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: Based on GroceryList.jsx cleanup (January 2026)
|
||||||
@ -1,5 +1,15 @@
|
|||||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import { addItem, getClassification, getItemByName, getList, getRecentlyBought, getSuggestions, markBought, updateItemImage, updateItemWithClassification } from "../api/list";
|
import {
|
||||||
|
addItem,
|
||||||
|
getClassification,
|
||||||
|
getItemByName,
|
||||||
|
getList,
|
||||||
|
getRecentlyBought,
|
||||||
|
getSuggestions,
|
||||||
|
markBought,
|
||||||
|
updateItemImage,
|
||||||
|
updateItemWithClassification
|
||||||
|
} from "../api/list";
|
||||||
import FloatingActionButton from "../components/common/FloatingActionButton";
|
import FloatingActionButton from "../components/common/FloatingActionButton";
|
||||||
import SortDropdown from "../components/common/SortDropdown";
|
import SortDropdown from "../components/common/SortDropdown";
|
||||||
import AddItemForm from "../components/forms/AddItemForm";
|
import AddItemForm from "../components/forms/AddItemForm";
|
||||||
@ -13,9 +23,11 @@ import { AuthContext } from "../context/AuthContext";
|
|||||||
import "../styles/pages/GroceryList.css";
|
import "../styles/pages/GroceryList.css";
|
||||||
import { findSimilarItems } from "../utils/stringSimilarity";
|
import { findSimilarItems } from "../utils/stringSimilarity";
|
||||||
|
|
||||||
|
|
||||||
export default function GroceryList() {
|
export default function GroceryList() {
|
||||||
const { role } = useContext(AuthContext);
|
const { role } = useContext(AuthContext);
|
||||||
|
|
||||||
|
// === State === //
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [recentlyBoughtItems, setRecentlyBoughtItems] = useState([]);
|
const [recentlyBoughtItems, setRecentlyBoughtItems] = useState([]);
|
||||||
const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(10);
|
const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(10);
|
||||||
@ -31,6 +43,8 @@ export default function GroceryList() {
|
|||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const [editingItem, setEditingItem] = useState(null);
|
const [editingItem, setEditingItem] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
// === Data Loading ===
|
||||||
const loadItems = async () => {
|
const loadItems = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await getList();
|
const res = await getList();
|
||||||
@ -39,6 +53,7 @@ export default function GroceryList() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const loadRecentlyBought = async () => {
|
const loadRecentlyBought = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getRecentlyBought();
|
const res = await getRecentlyBought();
|
||||||
@ -49,11 +64,14 @@ export default function GroceryList() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadItems();
|
loadItems();
|
||||||
loadRecentlyBought();
|
loadRecentlyBought();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// === Sorted Items Computation ===
|
||||||
const sortedItems = useMemo(() => {
|
const sortedItems = useMemo(() => {
|
||||||
const sorted = [...items];
|
const sorted = [...items];
|
||||||
|
|
||||||
@ -95,6 +113,8 @@ export default function GroceryList() {
|
|||||||
return sorted;
|
return sorted;
|
||||||
}, [items, sortMode]);
|
}, [items, sortMode]);
|
||||||
|
|
||||||
|
|
||||||
|
// === Suggestion Handler ===
|
||||||
const handleSuggest = async (text) => {
|
const handleSuggest = async (text) => {
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
setSuggestions([]);
|
setSuggestions([]);
|
||||||
@ -106,26 +126,21 @@ export default function GroceryList() {
|
|||||||
const lowerText = text.toLowerCase().trim();
|
const lowerText = text.toLowerCase().trim();
|
||||||
const exactMatch = allItems.find(item => item.item_name.toLowerCase() === lowerText);
|
const exactMatch = allItems.find(item => item.item_name.toLowerCase() === lowerText);
|
||||||
|
|
||||||
if (exactMatch) {
|
setButtonText(exactMatch ? "Add" : "Create + Add");
|
||||||
setButtonText("Add");
|
|
||||||
} else {
|
|
||||||
setButtonText("Create + Add");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let suggestions = await getSuggestions(text);
|
const suggestions = await getSuggestions(text);
|
||||||
suggestions = suggestions.data.map(s => s.item_name);
|
setSuggestions(suggestions.data.map(s => s.item_name));
|
||||||
setSuggestions(suggestions);
|
|
||||||
} catch {
|
} catch {
|
||||||
setSuggestions([]);
|
setSuggestions([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// === Item Addition Handlers ===
|
||||||
const handleAdd = useCallback(async (itemName, quantity) => {
|
const handleAdd = useCallback(async (itemName, quantity) => {
|
||||||
if (!itemName.trim()) return;
|
if (!itemName.trim()) return;
|
||||||
|
|
||||||
const lowerItemName = itemName.toLowerCase().trim();
|
|
||||||
|
|
||||||
let existingItem = null;
|
let existingItem = null;
|
||||||
try {
|
try {
|
||||||
const response = await getItemByName(itemName);
|
const response = await getItemByName(itemName);
|
||||||
@ -153,6 +168,7 @@ export default function GroceryList() {
|
|||||||
});
|
});
|
||||||
}, [recentlyBoughtItems]);
|
}, [recentlyBoughtItems]);
|
||||||
|
|
||||||
|
|
||||||
const processItemAddition = useCallback(async (itemName, quantity) => {
|
const processItemAddition = useCallback(async (itemName, quantity) => {
|
||||||
let existingItem = null;
|
let existingItem = null;
|
||||||
try {
|
try {
|
||||||
@ -162,7 +178,7 @@ export default function GroceryList() {
|
|||||||
existingItem = null;
|
existingItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingItem && existingItem.bought === false) {
|
if (existingItem?.bought === false) {
|
||||||
const currentQuantity = existingItem.quantity;
|
const currentQuantity = existingItem.quantity;
|
||||||
const newQuantity = currentQuantity + quantity;
|
const newQuantity = currentQuantity + quantity;
|
||||||
const yes = window.confirm(
|
const yes = window.confirm(
|
||||||
@ -196,11 +212,14 @@ export default function GroceryList() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// === Similar Item Modal Handlers ===
|
||||||
const handleSimilarCancel = useCallback(() => {
|
const handleSimilarCancel = useCallback(() => {
|
||||||
setShowSimilarModal(false);
|
setShowSimilarModal(false);
|
||||||
setSimilarItemSuggestion(null);
|
setSimilarItemSuggestion(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleSimilarNo = useCallback(async () => {
|
const handleSimilarNo = useCallback(async () => {
|
||||||
if (!similarItemSuggestion) return;
|
if (!similarItemSuggestion) return;
|
||||||
setShowSimilarModal(false);
|
setShowSimilarModal(false);
|
||||||
@ -208,6 +227,7 @@ export default function GroceryList() {
|
|||||||
setSimilarItemSuggestion(null);
|
setSimilarItemSuggestion(null);
|
||||||
}, [similarItemSuggestion, processItemAddition]);
|
}, [similarItemSuggestion, processItemAddition]);
|
||||||
|
|
||||||
|
|
||||||
const handleSimilarYes = useCallback(async () => {
|
const handleSimilarYes = useCallback(async () => {
|
||||||
if (!similarItemSuggestion) return;
|
if (!similarItemSuggestion) return;
|
||||||
setShowSimilarModal(false);
|
setShowSimilarModal(false);
|
||||||
@ -215,6 +235,8 @@ export default function GroceryList() {
|
|||||||
setSimilarItemSuggestion(null);
|
setSimilarItemSuggestion(null);
|
||||||
}, [similarItemSuggestion, processItemAddition]);
|
}, [similarItemSuggestion, processItemAddition]);
|
||||||
|
|
||||||
|
|
||||||
|
// === Add Details Modal Handlers ===
|
||||||
const handleAddDetailsConfirm = useCallback(async (imageFile, classification) => {
|
const handleAddDetailsConfirm = useCallback(async (imageFile, classification) => {
|
||||||
if (!pendingItem) return;
|
if (!pendingItem) return;
|
||||||
|
|
||||||
@ -243,6 +265,7 @@ export default function GroceryList() {
|
|||||||
}
|
}
|
||||||
}, [pendingItem]);
|
}, [pendingItem]);
|
||||||
|
|
||||||
|
|
||||||
const handleAddDetailsSkip = useCallback(async () => {
|
const handleAddDetailsSkip = useCallback(async () => {
|
||||||
if (!pendingItem) return;
|
if (!pendingItem) return;
|
||||||
|
|
||||||
@ -261,7 +284,8 @@ export default function GroceryList() {
|
|||||||
console.error("Failed to add item:", error);
|
console.error("Failed to add item:", error);
|
||||||
alert("Failed to add item. Please try again.");
|
alert("Failed to add item. Please try again.");
|
||||||
}
|
}
|
||||||
}, []);
|
}, [pendingItem]);
|
||||||
|
|
||||||
|
|
||||||
const handleAddDetailsCancel = useCallback(() => {
|
const handleAddDetailsCancel = useCallback(() => {
|
||||||
setShowAddDetailsModal(false);
|
setShowAddDetailsModal(false);
|
||||||
@ -271,14 +295,14 @@ export default function GroceryList() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// === Item Action Handlers ===
|
||||||
const handleBought = useCallback(async (id, quantity) => {
|
const handleBought = useCallback(async (id, quantity) => {
|
||||||
await markBought(id);
|
await markBought(id);
|
||||||
|
|
||||||
setItems(prevItems => prevItems.filter(item => item.id !== id));
|
setItems(prevItems => prevItems.filter(item => item.id !== id));
|
||||||
loadRecentlyBought();
|
loadRecentlyBought();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleImageAdded = useCallback(async (id, itemName, quantity, imageFile) => {
|
const handleImageAdded = useCallback(async (id, itemName, quantity, imageFile) => {
|
||||||
try {
|
try {
|
||||||
const response = await updateItemImage(id, itemName, quantity, imageFile);
|
const response = await updateItemImage(id, itemName, quantity, imageFile);
|
||||||
@ -300,6 +324,7 @@ export default function GroceryList() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleLongPress = useCallback(async (item) => {
|
const handleLongPress = useCallback(async (item) => {
|
||||||
if (![ROLES.ADMIN, ROLES.EDITOR].includes(role)) return;
|
if (![ROLES.ADMIN, ROLES.EDITOR].includes(role)) return;
|
||||||
|
|
||||||
@ -317,6 +342,8 @@ export default function GroceryList() {
|
|||||||
}
|
}
|
||||||
}, [role]);
|
}, [role]);
|
||||||
|
|
||||||
|
|
||||||
|
// === Edit Modal Handlers ===
|
||||||
const handleEditSave = useCallback(async (id, itemName, quantity, classification) => {
|
const handleEditSave = useCallback(async (id, itemName, quantity, classification) => {
|
||||||
try {
|
try {
|
||||||
const response = await updateItemWithClassification(id, itemName, quantity, classification);
|
const response = await updateItemWithClassification(id, itemName, quantity, classification);
|
||||||
@ -337,15 +364,18 @@ export default function GroceryList() {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update item:", error);
|
console.error("Failed to update item:", error);
|
||||||
throw error; // Re-throw to let modal handle it
|
throw error;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleEditCancel = useCallback(() => {
|
const handleEditCancel = useCallback(() => {
|
||||||
setShowEditModal(false);
|
setShowEditModal(false);
|
||||||
setEditingItem(null);
|
setEditingItem(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// === Helper Functions ===
|
||||||
const groupItemsByZone = (items) => {
|
const groupItemsByZone = (items) => {
|
||||||
const groups = {};
|
const groups = {};
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
@ -358,8 +388,10 @@ export default function GroceryList() {
|
|||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (loading) return <p>Loading...</p>;
|
if (loading) return <p>Loading...</p>;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="glist-body">
|
<div className="glist-body">
|
||||||
<div className="glist-container">
|
<div className="glist-container">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user