162 lines
5.7 KiB
TypeScript
162 lines
5.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Grocery } from '../types';
|
|
import { groceryApi } from '../services/api';
|
|
import AddGroceryModal from './AddGroceryModal';
|
|
|
|
const GroceryList: React.FC = () => {
|
|
const [groceries, setGroceries] = useState<Grocery[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [message, setMessage] = useState('');
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingGrocery, setEditingGrocery] = useState<Grocery | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetchGroceries();
|
|
}, []);
|
|
|
|
const fetchGroceries = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await groceryApi.getAll();
|
|
setGroceries(response.data);
|
|
} catch (error) {
|
|
console.error('Error fetching groceries:', error);
|
|
setMessage('Error loading groceries. Please try again.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: number) => {
|
|
if (window.confirm('Are you sure you want to delete this grocery?')) {
|
|
try {
|
|
await groceryApi.delete(id);
|
|
setMessage('Grocery deleted successfully!');
|
|
fetchGroceries();
|
|
setTimeout(() => setMessage(''), 3000);
|
|
} catch (error: any) {
|
|
console.error('Error deleting grocery:', error);
|
|
if (error.response?.status === 400) {
|
|
setMessage('Cannot delete grocery: products are still associated with this grocery.');
|
|
} else {
|
|
setMessage('Error deleting grocery. Please try again.');
|
|
}
|
|
setTimeout(() => setMessage(''), 5000);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleEdit = (grocery: Grocery) => {
|
|
setEditingGrocery(grocery);
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleModalClose = () => {
|
|
setIsModalOpen(false);
|
|
setEditingGrocery(null);
|
|
fetchGroceries();
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex justify-center items-center h-64">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto">
|
|
<div className="bg-white shadow rounded-lg">
|
|
<div className="px-4 py-5 sm:p-6">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
|
Groceries
|
|
</h3>
|
|
<button
|
|
onClick={() => setIsModalOpen(true)}
|
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
|
>
|
|
Add Grocery
|
|
</button>
|
|
</div>
|
|
|
|
{message && (
|
|
<div className={`mb-4 p-4 rounded-md ${
|
|
message.includes('Error') || message.includes('Cannot')
|
|
? 'bg-red-50 text-red-700'
|
|
: 'bg-green-50 text-green-700'
|
|
}`}>
|
|
{message}
|
|
</div>
|
|
)}
|
|
|
|
{groceries.length === 0 ? (
|
|
<div className="text-center py-8">
|
|
<p className="text-gray-500">No groceries found. Add your first grocery!</p>
|
|
</div>
|
|
) : (
|
|
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
|
|
<table className="min-w-full divide-y divide-gray-300">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Name
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Category
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Created
|
|
</th>
|
|
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{groceries.map((grocery) => (
|
|
<tr key={grocery.id} className="hover:bg-gray-50">
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
{grocery.name}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{grocery.category.name}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{new Date(grocery.created_at).toLocaleDateString()}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<button
|
|
onClick={() => handleEdit(grocery)}
|
|
className="text-indigo-600 hover:text-indigo-900 mr-4"
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
onClick={() => handleDelete(grocery.id)}
|
|
className="text-red-600 hover:text-red-900"
|
|
>
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{isModalOpen && (
|
|
<AddGroceryModal
|
|
grocery={editingGrocery}
|
|
onClose={handleModalClose}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default GroceryList;
|