diff --git a/src/App.js b/src/App.js index d8b0484..52848b8 100644 --- a/src/App.js +++ b/src/App.js @@ -8,6 +8,7 @@ import Items from './components/Items'; import Navbar from './components/Navbar'; // Correct import here import Admin from './components/Admin'; // Correct import here import { createContext } from 'react'; +import ErrorBoundary from './components/ErrorBoundary'; import './styles.css' export const AppContext = createContext(); @@ -26,10 +27,12 @@ function App() { return ( - - - - + + + + + + ); } diff --git a/src/components/ErrorBoundary.js b/src/components/ErrorBoundary.js new file mode 100644 index 0000000..09bb2d1 --- /dev/null +++ b/src/components/ErrorBoundary.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Alert, Button, Container } from '@mui/material'; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + render() { + if (this.state.hasError) { + return ( + + + Something went wrong + + + + ); + } + return this.props.children; + } +} + +export default ErrorBoundary; \ No newline at end of file diff --git a/src/components/Items.js b/src/components/Items.js index 74a6025..8af44ee 100644 --- a/src/components/Items.js +++ b/src/components/Items.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { Container, Button, @@ -27,6 +27,30 @@ import { useApiCall } from './hooks/useApiCall'; import { api } from '../services/api'; import ItemDetails from './ItemDetails'; +const Item = React.memo(({ item, onDelete, onEdit, itemImages }) => ( + + + + + {item.name} + {item.description} + + + + onEdit(item)} size="large" sx={{ mr: 1 }}> + + + + + onDelete(item.ID)} size="large" color="error"> + + + + + + +)); + export default function Items({ token }) { const { id: boxId } = useParams(); const [items, setItems] = useState([]); @@ -53,7 +77,7 @@ export default function Items({ token }) { const getItems = async () => { try { const response = await fetchItems(() => - api.items.getAll(token, boxId) + boxId ? api.items.getByBox(token, boxId) : api.items.getAll(token) ); setItems(response.data); @@ -103,7 +127,7 @@ export default function Items({ token }) { return imageName; }; - const handleSaveNewItem = async () => { + const handleSaveNewItem = useCallback(async () => { try { const newItemResponse = await createItem(() => api.items.create(token, { @@ -112,7 +136,7 @@ export default function Items({ token }) { box_id: parseInt(boxId, 10) }) ); - + if (newItemResponse.status === 200 && fileInputRef.current?.files?.[0]) { const imageFile = fileInputRef.current.files[0]; const newImageName = generateUniqueImageName(imageFile.name); @@ -120,34 +144,49 @@ export default function Items({ token }) { formData.append('image', new File([imageFile], newImageName, { type: imageFile.type, })); - + await uploadImage(() => api.items.uploadImage(token, newItemResponse.data.id, formData) ); + if (newItemResponse.data.id) { + try { + const imageResponse = await api.items.getImage(token, newItemResponse.data.id); + const reader = new FileReader(); + reader.onload = () => { + setItemImages(prev => ({ + ...prev, + [newItemResponse.data.id]: reader.result + })); + }; + reader.readAsDataURL(imageResponse.data); + } catch (err) { + setItemImages(prev => ({ + ...prev, + [newItemResponse.data.id]: '/default.jpg' + })); + } + } } - + handleCloseAddItemDialog(); - // Refresh items list const response = await fetchItems(() => - api.items.getAll(token, boxId) + boxId ? api.items.getByBox(token, boxId) : api.items.getAll(token) ); setItems(response.data); } catch (err) {} - }; + }, [token, boxId, newItemName, newItemDescription, createItem, uploadImage, fetchItems]); - const handleDeleteItem = async (itemId) => { + const handleDeleteItem = useCallback(async (itemId) => { try { - await deleteItem(() => - api.items.delete(token, itemId) - ); - setItems(items.filter(item => item.ID !== itemId)); + await deleteItem(() => api.items.delete(token, itemId)); + setItems(prev => prev.filter(item => item.ID !== itemId)); } catch (err) {} - }; + }, [token, deleteItem]); - const handleEditItem = (item) => { + const handleEditItem = useCallback((item) => { setEditingItem(item); - }; + }, []); const handleSaveEdit = async () => { setEditingItem(null); @@ -157,9 +196,12 @@ export default function Items({ token }) { setItems(response.data); }; - const filteredItems = items.filter(item => - item.name.toLowerCase().includes(searchQuery.toLowerCase()) || - item.description.toLowerCase().includes(searchQuery.toLowerCase()) + const filteredItems = useMemo(() => + items.filter(item => + item.name.toLowerCase().includes(searchQuery.toLowerCase()) || + item.description.toLowerCase().includes(searchQuery.toLowerCase()) + ), + [items, searchQuery] ); return ( @@ -251,40 +293,17 @@ export default function Items({ token }) { Actions - - {filteredItems.map((item) => ( - - - - - {item.name} - {item.description} - - - - handleEditItem(item)} - size="large" - sx={{ mr: 1 }} - > - - - - - handleDeleteItem(item.ID)} - size="large" - color="error" - disabled={deletingItem} - > - {deletingItem ? : } - - - - - - ))} - + + {filteredItems.map((item) => ( + + ))} + )}