Compare commits
2 Commits
2fadb2d991
...
b81379432b
| Author | SHA1 | Date | |
|---|---|---|---|
| b81379432b | |||
| 4f898054ff |
@ -4,7 +4,6 @@ import GroceryList from './components/GroceryList';
|
|||||||
import ShopList from './components/ShopList';
|
import ShopList from './components/ShopList';
|
||||||
import ShoppingEventForm from './components/ShoppingEventForm';
|
import ShoppingEventForm from './components/ShoppingEventForm';
|
||||||
import ShoppingEventList from './components/ShoppingEventList';
|
import ShoppingEventList from './components/ShoppingEventList';
|
||||||
import EditShoppingEvent from './components/EditShoppingEvent';
|
|
||||||
import Dashboard from './components/Dashboard';
|
import Dashboard from './components/Dashboard';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@ -65,7 +64,7 @@ function App() {
|
|||||||
<Route path="/groceries" element={<GroceryList />} />
|
<Route path="/groceries" element={<GroceryList />} />
|
||||||
<Route path="/shops" element={<ShopList />} />
|
<Route path="/shops" element={<ShopList />} />
|
||||||
<Route path="/shopping-events" element={<ShoppingEventList />} />
|
<Route path="/shopping-events" element={<ShoppingEventList />} />
|
||||||
<Route path="/shopping-events/:id/edit" element={<EditShoppingEvent />} />
|
<Route path="/shopping-events/:id/edit" element={<ShoppingEventForm />} />
|
||||||
<Route path="/add-purchase" element={<ShoppingEventForm />} />
|
<Route path="/add-purchase" element={<ShoppingEventForm />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -1,367 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
|
||||||
import { Shop, Grocery, ShoppingEvent, ShoppingEventCreate, GroceryInEvent } from '../types';
|
|
||||||
import { shopApi, groceryApi, shoppingEventApi } from '../services/api';
|
|
||||||
|
|
||||||
const EditShoppingEvent: React.FC = () => {
|
|
||||||
const { id } = useParams<{ id: string }>();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [shops, setShops] = useState<Shop[]>([]);
|
|
||||||
const [groceries, setGroceries] = useState<Grocery[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [loadingEvent, setLoadingEvent] = useState(true);
|
|
||||||
const [message, setMessage] = useState('');
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState<ShoppingEventCreate>({
|
|
||||||
shop_id: 0,
|
|
||||||
date: new Date().toISOString().split('T')[0],
|
|
||||||
total_amount: undefined,
|
|
||||||
notes: '',
|
|
||||||
groceries: []
|
|
||||||
});
|
|
||||||
|
|
||||||
const [selectedGroceries, setSelectedGroceries] = useState<GroceryInEvent[]>([]);
|
|
||||||
const [newGroceryItem, setNewGroceryItem] = useState<GroceryInEvent>({
|
|
||||||
grocery_id: 0,
|
|
||||||
amount: 1,
|
|
||||||
price: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchShops();
|
|
||||||
fetchGroceries();
|
|
||||||
if (id) {
|
|
||||||
fetchShoppingEvent(parseInt(id));
|
|
||||||
}
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
const fetchShoppingEvent = async (eventId: number) => {
|
|
||||||
try {
|
|
||||||
setLoadingEvent(true);
|
|
||||||
const response = await shoppingEventApi.getById(eventId);
|
|
||||||
const event = response.data;
|
|
||||||
|
|
||||||
// Use the date directly if it's already in YYYY-MM-DD format, otherwise format it
|
|
||||||
let formattedDate = event.date;
|
|
||||||
if (event.date.includes('T') || event.date.length > 10) {
|
|
||||||
// If the date includes time or is longer than YYYY-MM-DD, extract just the date part
|
|
||||||
formattedDate = event.date.split('T')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
setFormData({
|
|
||||||
shop_id: event.shop.id,
|
|
||||||
date: formattedDate,
|
|
||||||
total_amount: event.total_amount,
|
|
||||||
notes: event.notes || '',
|
|
||||||
groceries: []
|
|
||||||
});
|
|
||||||
|
|
||||||
setSelectedGroceries(event.groceries.map(g => ({
|
|
||||||
grocery_id: g.id,
|
|
||||||
amount: g.amount,
|
|
||||||
price: g.price
|
|
||||||
})));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching shopping event:', error);
|
|
||||||
setMessage('Error loading shopping event. Please try again.');
|
|
||||||
} finally {
|
|
||||||
setLoadingEvent(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchShops = async () => {
|
|
||||||
try {
|
|
||||||
const response = await shopApi.getAll();
|
|
||||||
setShops(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching shops:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchGroceries = async () => {
|
|
||||||
try {
|
|
||||||
const response = await groceryApi.getAll();
|
|
||||||
setGroceries(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching groceries:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addGroceryToEvent = () => {
|
|
||||||
if (newGroceryItem.grocery_id > 0 && newGroceryItem.amount > 0 && newGroceryItem.price > 0) {
|
|
||||||
setSelectedGroceries([...selectedGroceries, { ...newGroceryItem }]);
|
|
||||||
setNewGroceryItem({ grocery_id: 0, amount: 1, price: 0 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeGroceryFromEvent = (index: number) => {
|
|
||||||
setSelectedGroceries(selectedGroceries.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const editGroceryFromEvent = (index: number) => {
|
|
||||||
const groceryToEdit = selectedGroceries[index];
|
|
||||||
// Load the grocery data into the input fields
|
|
||||||
setNewGroceryItem({
|
|
||||||
grocery_id: groceryToEdit.grocery_id,
|
|
||||||
amount: groceryToEdit.amount,
|
|
||||||
price: groceryToEdit.price
|
|
||||||
});
|
|
||||||
// Remove the item from the selected list
|
|
||||||
setSelectedGroceries(selectedGroceries.filter((_, i) => i !== index));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setLoading(true);
|
|
||||||
setMessage('');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const eventData = {
|
|
||||||
...formData,
|
|
||||||
groceries: selectedGroceries
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Sending event data:', eventData);
|
|
||||||
const response = await shoppingEventApi.update(parseInt(id!), eventData);
|
|
||||||
console.log('Response:', response);
|
|
||||||
setMessage('Shopping event updated successfully!');
|
|
||||||
|
|
||||||
// Navigate back to shopping events list after a short delay
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate('/shopping-events');
|
|
||||||
}, 1500);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Full error object:', error);
|
|
||||||
setMessage('Error updating shopping event. Please try again.');
|
|
||||||
console.error('Error:', error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getGroceryName = (id: number) => {
|
|
||||||
const grocery = groceries.find(g => g.id === id);
|
|
||||||
return grocery ? grocery.name : 'Unknown';
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loadingEvent) {
|
|
||||||
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">
|
|
||||||
Edit Shopping Event
|
|
||||||
</h3>
|
|
||||||
<button
|
|
||||||
onClick={() => navigate('/shopping-events')}
|
|
||||||
className="text-gray-500 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
← Back to Shopping Events
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{message && (
|
|
||||||
<div className={`mb-4 p-4 rounded-md ${
|
|
||||||
message.includes('Error')
|
|
||||||
? 'bg-red-50 text-red-700'
|
|
||||||
: 'bg-green-50 text-green-700'
|
|
||||||
}`}>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
|
||||||
{/* Shop Selection */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Shop
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={formData.shop_id}
|
|
||||||
onChange={(e) => setFormData({...formData, shop_id: parseInt(e.target.value)})}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value={0}>Select a shop</option>
|
|
||||||
{shops.map(shop => (
|
|
||||||
<option key={shop.id} value={shop.id}>
|
|
||||||
{shop.name} - {shop.city}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Date */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Date
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={formData.date}
|
|
||||||
onChange={(e) => setFormData({...formData, date: e.target.value})}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add Groceries Section */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Add Groceries
|
|
||||||
</label>
|
|
||||||
<div className="flex space-x-2 mb-4">
|
|
||||||
<div className="flex-1">
|
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
||||||
Grocery
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={newGroceryItem.grocery_id}
|
|
||||||
onChange={(e) => setNewGroceryItem({...newGroceryItem, grocery_id: parseInt(e.target.value)})}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
>
|
|
||||||
<option value={0}>Select a grocery</option>
|
|
||||||
{groceries.map(grocery => (
|
|
||||||
<option key={grocery.id} value={grocery.id}>
|
|
||||||
{grocery.name} ({grocery.category})
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="w-24">
|
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
||||||
Amount
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="1"
|
|
||||||
min="1"
|
|
||||||
placeholder="1"
|
|
||||||
value={newGroceryItem.amount}
|
|
||||||
onChange={(e) => setNewGroceryItem({...newGroceryItem, amount: parseFloat(e.target.value)})}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-24">
|
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
||||||
Price ($)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
placeholder="0.00"
|
|
||||||
value={newGroceryItem.price}
|
|
||||||
onChange={(e) => setNewGroceryItem({...newGroceryItem, price: parseFloat(e.target.value)})}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={addGroceryToEvent}
|
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Selected Groceries */}
|
|
||||||
{selectedGroceries.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Selected Groceries
|
|
||||||
</label>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{selectedGroceries.map((item, index) => (
|
|
||||||
<div key={index} className="flex items-center justify-between bg-gray-50 p-3 rounded">
|
|
||||||
<span>
|
|
||||||
{getGroceryName(item.grocery_id)} - {item.amount} × ${item.price.toFixed(2)} = ${(item.amount * item.price).toFixed(2)}
|
|
||||||
</span>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => editGroceryFromEvent(index)}
|
|
||||||
className="text-blue-600 hover:text-blue-800"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => removeGroceryFromEvent(index)}
|
|
||||||
className="text-red-600 hover:text-red-800"
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Total Amount */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Total Amount (optional)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
placeholder="Leave empty to auto-calculate"
|
|
||||||
value={formData.total_amount || ''}
|
|
||||||
onChange={(e) => setFormData({...formData, total_amount: e.target.value ? parseFloat(e.target.value) : undefined})}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Notes */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Notes (optional)
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
value={formData.notes}
|
|
||||||
onChange={(e) => setFormData({...formData, notes: e.target.value})}
|
|
||||||
rows={3}
|
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
placeholder="Any additional notes about this shopping event..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Submit Button */}
|
|
||||||
<div className="flex justify-end space-x-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => navigate('/shopping-events')}
|
|
||||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={loading || formData.shop_id === 0}
|
|
||||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
{loading ? 'Updating...' : 'Update Shopping Event'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditShoppingEvent;
|
|
||||||
@ -1,13 +1,19 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Shop, Grocery, ShoppingEventCreate, GroceryInEvent } from '../types';
|
import { Shop, Grocery, ShoppingEventCreate, GroceryInEvent } from '../types';
|
||||||
import { shopApi, groceryApi, shoppingEventApi } from '../services/api';
|
import { shopApi, groceryApi, shoppingEventApi } from '../services/api';
|
||||||
|
|
||||||
const ShoppingEventForm: React.FC = () => {
|
const ShoppingEventForm: React.FC = () => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [shops, setShops] = useState<Shop[]>([]);
|
const [shops, setShops] = useState<Shop[]>([]);
|
||||||
const [groceries, setGroceries] = useState<Grocery[]>([]);
|
const [groceries, setGroceries] = useState<Grocery[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingEvent, setLoadingEvent] = useState(false);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
const isEditMode = Boolean(id);
|
||||||
|
|
||||||
const [formData, setFormData] = useState<ShoppingEventCreate>({
|
const [formData, setFormData] = useState<ShoppingEventCreate>({
|
||||||
shop_id: 0,
|
shop_id: 0,
|
||||||
date: new Date().toISOString().split('T')[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
@ -26,7 +32,10 @@ const ShoppingEventForm: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchShops();
|
fetchShops();
|
||||||
fetchGroceries();
|
fetchGroceries();
|
||||||
}, []);
|
if (isEditMode && id) {
|
||||||
|
fetchShoppingEvent(parseInt(id));
|
||||||
|
}
|
||||||
|
}, [id, isEditMode]);
|
||||||
|
|
||||||
const fetchShops = async () => {
|
const fetchShops = async () => {
|
||||||
try {
|
try {
|
||||||
@ -46,8 +55,42 @@ const ShoppingEventForm: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchShoppingEvent = async (eventId: number) => {
|
||||||
|
try {
|
||||||
|
setLoadingEvent(true);
|
||||||
|
const response = await shoppingEventApi.getById(eventId);
|
||||||
|
const event = response.data;
|
||||||
|
|
||||||
|
// Use the date directly if it's already in YYYY-MM-DD format, otherwise format it
|
||||||
|
let formattedDate = event.date;
|
||||||
|
if (event.date.includes('T') || event.date.length > 10) {
|
||||||
|
// If the date includes time or is longer than YYYY-MM-DD, extract just the date part
|
||||||
|
formattedDate = event.date.split('T')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
shop_id: event.shop.id,
|
||||||
|
date: formattedDate,
|
||||||
|
total_amount: event.total_amount,
|
||||||
|
notes: event.notes || '',
|
||||||
|
groceries: []
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectedGroceries(event.groceries.map(g => ({
|
||||||
|
grocery_id: g.id,
|
||||||
|
amount: g.amount,
|
||||||
|
price: g.price
|
||||||
|
})));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching shopping event:', error);
|
||||||
|
setMessage('Error loading shopping event. Please try again.');
|
||||||
|
} finally {
|
||||||
|
setLoadingEvent(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const addGroceryToEvent = () => {
|
const addGroceryToEvent = () => {
|
||||||
if (newGroceryItem.grocery_id > 0 && newGroceryItem.amount > 0 && newGroceryItem.price > 0) {
|
if (newGroceryItem.grocery_id > 0 && newGroceryItem.amount > 0 && newGroceryItem.price >= 0) {
|
||||||
setSelectedGroceries([...selectedGroceries, { ...newGroceryItem }]);
|
setSelectedGroceries([...selectedGroceries, { ...newGroceryItem }]);
|
||||||
setNewGroceryItem({ grocery_id: 0, amount: 1, price: 0 });
|
setNewGroceryItem({ grocery_id: 0, amount: 1, price: 0 });
|
||||||
}
|
}
|
||||||
@ -80,10 +123,22 @@ const ShoppingEventForm: React.FC = () => {
|
|||||||
groceries: selectedGroceries
|
groceries: selectedGroceries
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isEditMode) {
|
||||||
|
// Update existing event
|
||||||
|
console.log('Updating event data:', eventData);
|
||||||
|
await shoppingEventApi.update(parseInt(id!), eventData);
|
||||||
|
setMessage('Shopping event updated successfully!');
|
||||||
|
|
||||||
|
// Navigate back to shopping events list after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate('/shopping-events');
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
// Create new event
|
||||||
await shoppingEventApi.create(eventData);
|
await shoppingEventApi.create(eventData);
|
||||||
setMessage('Shopping event created successfully!');
|
setMessage('Shopping event created successfully!');
|
||||||
|
|
||||||
// Reset form
|
// Reset form for add mode
|
||||||
setFormData({
|
setFormData({
|
||||||
shop_id: 0,
|
shop_id: 0,
|
||||||
date: new Date().toISOString().split('T')[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
@ -92,9 +147,10 @@ const ShoppingEventForm: React.FC = () => {
|
|||||||
groceries: []
|
groceries: []
|
||||||
});
|
});
|
||||||
setSelectedGroceries([]);
|
setSelectedGroceries([]);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setMessage('Error creating shopping event. Please try again.');
|
console.error('Full error object:', error);
|
||||||
console.error('Error:', error);
|
setMessage(`Error ${isEditMode ? 'updating' : 'creating'} shopping event. Please try again.`);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -105,13 +161,31 @@ const ShoppingEventForm: React.FC = () => {
|
|||||||
return grocery ? grocery.name : 'Unknown';
|
return grocery ? grocery.name : 'Unknown';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loadingEvent) {
|
||||||
|
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 (
|
return (
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="bg-white shadow rounded-lg">
|
<div className="bg-white shadow rounded-lg">
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="px-4 py-5 sm:p-6">
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
Add New Event
|
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{isEditMode ? 'Edit Shopping Event' : 'Add New Event'}
|
||||||
</h3>
|
</h3>
|
||||||
|
{isEditMode && (
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/shopping-events')}
|
||||||
|
className="text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
← Back to Shopping Events
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
<div className={`mb-4 p-4 rounded-md ${
|
<div className={`mb-4 p-4 rounded-md ${
|
||||||
@ -282,13 +356,29 @@ const ShoppingEventForm: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<div>
|
<div className="flex justify-end space-x-3">
|
||||||
|
{isEditMode && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate('/shopping-events')}
|
||||||
|
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || formData.shop_id === 0 || selectedGroceries.length === 0}
|
disabled={loading || formData.shop_id === 0 || selectedGroceries.length === 0}
|
||||||
className="w-full bg-blue-500 hover:bg-blue-700 disabled:bg-gray-300 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
className={`px-4 py-2 text-sm font-medium text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed ${
|
||||||
|
isEditMode
|
||||||
|
? 'bg-blue-600 hover:bg-blue-700'
|
||||||
|
: 'w-full bg-blue-500 hover:bg-blue-700 font-bold py-2 px-4 focus:outline-none focus:shadow-outline'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{loading ? 'Creating...' : 'Create Shopping Event'}
|
{loading
|
||||||
|
? (isEditMode ? 'Updating...' : 'Creating...')
|
||||||
|
: (isEditMode ? 'Update Shopping Event' : 'Create Shopping Event')
|
||||||
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user