Handling of total amount in shopping events
This commit is contained in:
parent
8b2e4408fc
commit
19a410d553
@ -200,7 +200,7 @@ def create_shopping_event(event: schemas.ShoppingEventCreate, db: Session = Depe
|
||||
|
||||
@app.get("/shopping-events/", response_model=List[schemas.ShoppingEventResponse])
|
||||
def read_shopping_events(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
events = db.query(models.ShoppingEvent).offset(skip).limit(limit).all()
|
||||
events = db.query(models.ShoppingEvent).order_by(models.ShoppingEvent.created_at.desc()).offset(skip).limit(limit).all()
|
||||
return [build_shopping_event_response(event, db) for event in events]
|
||||
|
||||
@app.get("/shopping-events/{event_id}", response_model=schemas.ShoppingEventResponse)
|
||||
|
||||
@ -53,7 +53,7 @@ class Shop(ShopBase):
|
||||
class GroceryInEvent(BaseModel):
|
||||
grocery_id: int
|
||||
amount: float = Field(..., gt=0)
|
||||
price: float = Field(..., gt=0) # Price at the time of this shopping event
|
||||
price: float = Field(..., ge=0) # Price at the time of this shopping event (allow free items)
|
||||
|
||||
class GroceryWithEventData(BaseModel):
|
||||
id: int
|
||||
|
||||
@ -28,6 +28,7 @@ const ShoppingEventForm: React.FC = () => {
|
||||
amount: 1,
|
||||
price: 0
|
||||
});
|
||||
const [autoCalculate, setAutoCalculate] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchShops();
|
||||
@ -37,6 +38,23 @@ const ShoppingEventForm: React.FC = () => {
|
||||
}
|
||||
}, [id, isEditMode]);
|
||||
|
||||
// Calculate total amount from selected groceries
|
||||
const calculateTotal = (groceries: GroceryInEvent[]): number => {
|
||||
const total = groceries.reduce((total, item) => total + (item.amount * item.price), 0);
|
||||
return Math.round(total * 100) / 100; // Round to 2 decimal places to avoid floating-point errors
|
||||
};
|
||||
|
||||
// Update total amount whenever selectedGroceries changes
|
||||
useEffect(() => {
|
||||
if (autoCalculate) {
|
||||
const calculatedTotal = calculateTotal(selectedGroceries);
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
total_amount: calculatedTotal > 0 ? calculatedTotal : undefined
|
||||
}));
|
||||
}
|
||||
}, [selectedGroceries, autoCalculate]);
|
||||
|
||||
const fetchShops = async () => {
|
||||
try {
|
||||
const response = await shopApi.getAll();
|
||||
@ -68,6 +86,20 @@ const ShoppingEventForm: React.FC = () => {
|
||||
formattedDate = event.date.split('T')[0];
|
||||
}
|
||||
|
||||
// Map groceries to the format we need
|
||||
const mappedGroceries = event.groceries.map(g => ({
|
||||
grocery_id: g.id,
|
||||
amount: g.amount,
|
||||
price: g.price
|
||||
}));
|
||||
|
||||
// Calculate the sum of all groceries
|
||||
const calculatedTotal = calculateTotal(mappedGroceries);
|
||||
|
||||
// Check if existing total matches calculated total (with small tolerance for floating point)
|
||||
const existingTotal = event.total_amount || 0;
|
||||
const totalMatches = Math.abs(existingTotal - calculatedTotal) < 0.01;
|
||||
|
||||
setFormData({
|
||||
shop_id: event.shop.id,
|
||||
date: formattedDate,
|
||||
@ -76,11 +108,8 @@ const ShoppingEventForm: React.FC = () => {
|
||||
groceries: []
|
||||
});
|
||||
|
||||
setSelectedGroceries(event.groceries.map(g => ({
|
||||
grocery_id: g.id,
|
||||
amount: g.amount,
|
||||
price: g.price
|
||||
})));
|
||||
setSelectedGroceries(mappedGroceries);
|
||||
setAutoCalculate(totalMatches); // Enable auto-calc if totals match, disable if they don't
|
||||
} catch (error) {
|
||||
console.error('Error fetching shopping event:', error);
|
||||
setMessage('Error loading shopping event. Please try again.');
|
||||
@ -303,9 +332,14 @@ const ShoppingEventForm: React.FC = () => {
|
||||
<h4 className="font-medium text-gray-700 mb-2">Selected Items:</h4>
|
||||
{selectedGroceries.map((item, index) => (
|
||||
<div key={index} className="flex justify-between items-center py-2 border-b last:border-b-0">
|
||||
<span>
|
||||
{getGroceryName(item.grocery_id)} x {item.amount} @ ${item.price.toFixed(2)}
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm text-gray-900">
|
||||
{getGroceryName(item.grocery_id)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-600">
|
||||
{item.amount} × ${item.price.toFixed(2)} = ${(item.amount * item.price).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
@ -330,18 +364,43 @@ const ShoppingEventForm: React.FC = () => {
|
||||
|
||||
{/* Total Amount */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Total Amount (optional)
|
||||
</label>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Total Amount
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoCalculate}
|
||||
onChange={(e) => setAutoCalculate(e.target.checked)}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"></div>
|
||||
</label>
|
||||
<span className={`text-xs px-2 py-1 rounded ${autoCalculate ? 'text-green-600 bg-green-50' : 'text-gray-500 bg-gray-100'}`}>
|
||||
Auto-calculated
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
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"
|
||||
onChange={(e) => {
|
||||
setFormData({...formData, total_amount: e.target.value ? parseFloat(e.target.value) : undefined});
|
||||
setAutoCalculate(false); // Disable auto-calculation when manually editing
|
||||
}}
|
||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 text-right"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{autoCalculate
|
||||
? "This field is automatically calculated from your selected items. You can manually edit it if needed."
|
||||
: "Auto-calculation is disabled. You can manually enter the total amount or enable auto-calculation above."
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user