import React, { useEffect, useState, useCallback, useRef } from 'react'; import { Container, List, ListItem, ListItemText, TextField, Button, IconButton, Typography, Avatar, ListItemAvatar, Dialog, DialogTitle, DialogContent, DialogActions, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Box, Tooltip } from '@mui/material'; import { Delete as DeleteIcon, Edit as EditIcon } from '@mui/icons-material'; import axios from 'axios'; import { useParams, useLocation } from 'react-router-dom'; import ItemDetails from './ItemDetails'; import { PRIMARY_COLOR, SECONDARY_COLOR } from '../App'; export default function Items({ token }) { const { id: boxId } = useParams(); const [items, setItems] = useState([]); const [newItemName, setNewItemName] = useState(''); const [newItemDescription, setNewItemDescription] = useState(''); // const [newItemImagePath, setNewItemImagePath] = useState('/images/default.jpg'); const [editingItem, setEditingItem] = useState(null); const location = useLocation(); const boxName = location.state?.boxName || 'Unknown Box'; // const boxID = location.state?.boxId; // used in handleClose function const [itemImages, setItemImages] = useState({}); const fileInputRef = useRef(null); const [openAddItemDialog, setOpenAddItemDialog] = useState(false); // For Add Item dialog const { id } = useParams(); const boxID = id; const url = boxId === undefined ? `${process.env.REACT_APP_API_URL}/api/v1/items` : `${process.env.REACT_APP_API_URL}/api/v1/boxes/${boxId}/items`; const [searchQuery, setSearchQuery] = useState(''); const debugLog = (message) => { if (process.env.DEBUG_API) { console.log(message); } }; debugLog("Box ID: " + boxID); // const handleSelectItem = (item) => { // setSelectedItem(item); // }; const handleAddItem = () => { setOpenAddItemDialog(true); }; const handleCloseAddItemDialog = () => { setOpenAddItemDialog(false); setNewItemName(''); setNewItemDescription(''); // setNewItemImagePath(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; /** * This function takes an image name and returns a unique image name. * The purpose of this function is to prevent overwriting of images with the same name. * If the image name is 'image.jpg', a random string is generated and appended to the image name. * This is used to prevent overwriting of images with the same name. * For example, if an image named 'image.jpg' is uploaded, the function will return 'image_8xgu6hcu.jpg' * This ensures that the image will not overwrite any existing image with the same name. * @param {string} imageName - The name of the image * @return {string} - The unique image name */ const generateUniqueImageName = (imageName) => { if (imageName.toLowerCase() === 'image.jpg') { // Generate a random string const randomString = Math.random().toString(36).substr(2, 9); // Append the random string to the image name return `image_${randomString}.jpg`; } // Return the original image name if it's not 'image.jpg' return imageName; }; const handleImageUpload = async (itemId, imageFile, newImageName) => { const formData = new FormData(); //const imageFile = fileInputRef.current.files[0]; //const newImageName = generateUniqueImageName(imageFile.name); formData.append('image', new File([imageFile], newImageName, { type: imageFile.type, })); // Create a new file with the unique name // eslint-disable-next-line const newImageFile = new File([imageFile], newImageName, { type: imageFile.type, }); try { const response = await axios.post(`${process.env.REACT_APP_API_URL}/api/v1/items/${itemId}/upload`, formData, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'multipart/form-data' } }); // console.log('Image uploaded successfully!'); return response.data.imagePath; // Indicate successful upload } catch (error) { console.error('Image upload failed:', error); return null; // Indicate upload failure } }; /** * Handle saving a new item. * * This function first creates the item without the image, then if the item creation is successful, * it uploads the image associated with the item. * * @return {Promise} */ const handleSaveNewItem = async () => { try { // Step 1: Create the item first // This sends a request to the API to create a new item with the // name and description provided. The box_id is set to the selected // box ID. const newItemResponse = await axios.post(`${process.env.REACT_APP_API_URL}/api/v1/items`, { name: newItemName, description: newItemDescription, box_id: parseInt(boxId, 10) // Ensure boxId is converted to an integer }, { headers: { Authorization: `Bearer ${token}` } }); console.log('New item created:', newItemResponse.status); // Step 2: If item creation is successful, upload the image if (newItemResponse.status === 200 && fileInputRef.current.files[0]) { // Get the ID of the newly created item const newItemId = newItemResponse.data.id; // Get the image file that was uploaded const imageFile = fileInputRef.current.files[0]; // Generate a unique image name by appending a random string // to the image file name. This is used to prevent overwriting // of images with the same name. const newImageName = generateUniqueImageName(imageFile.name); // Upload the image file with the unique name to the server const uploadedImagePath = await handleImageUpload(newItemId, fileInputRef.current.files[0], newImageName); if (uploadedImagePath) { // The image was uploaded successfully. Log the uploaded image path console.log("Image path to save:", uploadedImagePath); // You might want to update your item in the backend with the image path // For example: // await axios.put(...); } else { // The image upload failed. Log an error message console.error('Failed to upload image for the new item.'); } } // Close the add item dialog handleCloseAddItemDialog(); // Fetch the items again to get the latest data fetchItems(); } catch (error) { // Catch any errors that may occur during the item creation // and image upload process. Log the error message console.error('Error adding item:', error); } }; //const [selectedItem, setSelectedItem] = React.useState(null); const handleCloseItemDetails = () => { setEditingItem(null); // Close the ItemDetails modal }; const handleImageError = (e) => { if (e.target.src.startsWith('data:image/')) { console.error("Default image failed to load. Check the file path."); return; } const reader = new FileReader(); reader.onload = () => { e.target.onerror = null; e.target.src = reader.result; }; fetch('/default.jpg') .then(res => res.blob()) .then(blob => reader.readAsDataURL(blob)) .catch(error => console.error("Error loading default image:", error)); }; const getImageSrc = useCallback((itemId) => { return axios.get(`${process.env.REACT_APP_API_URL}/api/v1/items/${itemId}/image`, { headers: { Authorization: `Bearer ${token}` }, responseType: 'blob' }) .then(response => { if (response.status === 200) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(response.data); }); } else { throw new Error('Image fetch failed'); } }) .catch(() => { return new Promise((resolve, reject) => { const img = new Image(); img.src = '/default.jpg'; img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); resolve(canvas.toDataURL()); }; img.onerror = reject; }); }); }, [token]); const fetchItems = useCallback(() => { axios.get( url, { headers: { Authorization: `Bearer ${token}` } }).then(response => { setItems(response.data); // Fetch images for each item response.data.forEach(item => { getImageSrc(item.ID).then(imageDataUrl => { setItemImages(prevItemImages => ({ ...prevItemImages, [item.ID]: imageDataUrl })); }); }); }); }, [token, getImageSrc, url]); // lint says I don't need boxId here useEffect(() => { fetchItems(); }, [boxId, token, fetchItems]); // const handleAddItem = () => { // const formData = new FormData(); // formData.append('name', newItemName); // formData.append('description', newItemDescription); // formData.append('box_id', parseInt(boxId, 10)); // // Append image only if a new one is selected // if (fileInputRef.current.files[0]) { // formData.append('image', fileInputRef.current.files[0]); // } // axios.post(`${process.env.REACT_APP_API_URL}/items`, formData, { // headers: { // Authorization: `Bearer ${token}`, // 'Content-Type': 'multipart/form-data' // Important for file uploads // } // }).then(() => { // setNewItemName(''); // setNewItemDescription(''); // setNewItemImagePath(''); // // Clear the file input // if (fileInputRef.current) { // fileInputRef.current.value = ''; // } // fetchItems(); // }); // }; const handleDeleteItem = (itemId) => { axios.delete(`${process.env.REACT_APP_API_URL}/api/v1/items/${itemId}`, { headers: { Authorization: `Bearer ${token}` } }).then(() => { fetchItems(); }); }; const handleEditItem = (item) => { setEditingItem(item); }; const handleSaveEdit = () => { setEditingItem(null); fetchItems(); }; return ( setSearchQuery(e.target.value)} />

Items in Box: {boxName === "Unknown Box" ? "All Boxes" : `${boxName} (${items.length} items)`}

{/* Dialog for adding new item */} Add New Item setNewItemName(e.target.value)} /> setNewItemDescription(e.target.value)} /> {editingItem ? ( ) : ( Image Name Description Actions {items .filter(item => item.name.toLowerCase().includes(searchQuery.toLowerCase()) || item.description.toLowerCase().includes(searchQuery.toLowerCase()) ) .map((item) => ( {item.name} {item.description} handleEditItem(item)} size="large" sx={{ mr: 1 }} > handleDeleteItem(item.ID)} size="large" color="error" > ))}
)}
); }