boxes-fe/src/components/Items.js

346 lines
10 KiB
JavaScript

import React, { useEffect, useState, useCallback, useRef } from 'react';
import {
Container,
List,
ListItem,
ListItemText,
TextField,
Button,
IconButton,
Typography,
Avatar,
ListItemAvatar,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
} 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}/items` : `${process.env.REACT_APP_API_URL}/boxes/${boxId}/items`;
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 = '';
}
};
const handleImageUpload = async (itemId, imageFile) => {
const formData = new FormData();
formData.append('image', imageFile);
try {
const response = await axios.post(`${process.env.REACT_APP_API_URL}/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
}
};
const handleSaveNewItem = async () => {
try {
// 1. Create the item first
const newItemResponse = await axios.post(`${process.env.REACT_APP_API_URL}/items`, {
name: newItemName,
description: newItemDescription,
box_id: parseInt(boxId, 10)
}, {
headers: {
Authorization: `Bearer ${token}`
}
});
//console.log('New item created:', newItemResponse.status);
// 2. If item creation is successful, upload the image
if (newItemResponse.status === 200 && fileInputRef.current.files[0]) {
const newItemId = newItemResponse.data.id;
const uploadedImagePath = await handleImageUpload(newItemId, fileInputRef.current.files[0]);
if (uploadedImagePath) {
// 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 {
// Handle image upload failure
console.error('Failed to upload image for the new item.');
}
}
handleCloseAddItemDialog();
fetchItems();
} catch (error) {
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}/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}/items/${itemId}`, {
headers: { Authorization: `Bearer ${token}` }
}).then(() => {
fetchItems();
});
};
const handleEditItem = (item) => {
setEditingItem(item);
};
const handleSaveEdit = () => {
setEditingItem(null);
fetchItems();
};
return (
<Container>
<h2>Items in Box: {boxName === "Unknown Box" ? "All Boxes" : boxName}</h2>
<input
type="file"
accept="image/*"
ref={fileInputRef}
style={{ display: 'none' }}
id="newItemImageUpload" // Unique ID
/>
<Button sx={{ backgroundColor: PRIMARY_COLOR, borderBottom: '1px solid', borderColor: '#444', color: SECONDARY_COLOR }}variant="contained" color="primary" onClick={handleAddItem}>
Add Item
</Button>
{/* Dialog for adding new item */}
<Dialog open={openAddItemDialog} onClose={handleCloseAddItemDialog}>
<DialogTitle>Add New Item</DialogTitle>
<DialogContent>
<TextField
label="Item Name"
variant="outlined"
fullWidth
margin="normal"
value={newItemName}
onChange={(e) => setNewItemName(e.target.value)}
/>
<TextField
label="Item Description"
variant="outlined"
fullWidth
margin="normal"
value={newItemDescription}
onChange={(e) => setNewItemDescription(e.target.value)}
/>
<input
type="file"
accept="image/*"
ref={fileInputRef}
style={{ display: 'block', margin: '10px 0' }} // Style as needed
id="newItemImageUpload"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseAddItemDialog}>Cancel</Button>
<Button onClick={handleSaveNewItem} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
{editingItem ? (
<ItemDetails
item={editingItem}
token={token}
onSave={handleSaveEdit}
onClose={handleCloseItemDetails}
boxId={boxId}
/>
) : (
<List>
{items.map((item) => (
<ListItem key={item.ID} secondaryAction={
<>
<IconButton edge="end" onClick={() => handleEditItem(item)}>
<EditIcon />
</IconButton>
<IconButton edge="end" onClick={() => handleDeleteItem(item.ID)}>
<DeleteIcon />
</IconButton>
</>
}>
<ListItemAvatar>
<Avatar
src={itemImages[item.ID]}
alt={item.name}
onError={handleImageError}
/>
</ListItemAvatar>
<ListItemText
primary={item.name}
secondary={
<>
<Typography variant="body2">{item.description}</Typography>
{item.image_path && (
<Typography variant="caption">Image: {item.image_path}</Typography>
)}
</>
}
/>
</ListItem>
))}
</List>
)}
</Container>
);
}