diff --git a/backend/controllers/receiptController.js b/backend/controllers/receiptController.js index e20be11..42c63ae 100644 --- a/backend/controllers/receiptController.js +++ b/backend/controllers/receiptController.js @@ -75,19 +75,6 @@ const uploadReceipt = async (req, res) => { const savedReceipt = await newReceipt.save(); - // Automatically create a corresponding expense transaction - if (savedReceipt) { - const newTransaction = new IncomeExpense({ - user: req.user.id, - name: savedReceipt.extractedData.merchant, - category: savedReceipt.extractedData.category, - cost: savedReceipt.extractedData.amount, - addedOn: savedReceipt.extractedData.date, - isIncome: false, - }); - await newTransaction.save(); - } - res.status(201).json(savedReceipt); } catch (error) { console.error("Error with Gemini API:", error); @@ -103,6 +90,65 @@ const uploadReceipt = async (req, res) => { } }; +// @desc Save transaction after user confirmation and edits +// @route POST /api/receipts/save-transaction +// @access Private +const saveTransactionFromReceipt = async (req, res) => { + try { + const { receiptId, transactionData } = req.body; + + // Validate required fields + if (!receiptId || !transactionData) { + return res.status(400).json({ message: 'Receipt ID and transaction data are required' }); + } + + // Verify the receipt belongs to the user + const receipt = await Receipt.findOne({ _id: receiptId, user: req.user.id }); + if (!receipt) { + return res.status(404).json({ message: 'Receipt not found' }); + } + + // Validate and parse the date + const transactionDate = new Date(transactionData.addedOn); + if (isNaN(transactionDate.getTime())) { + return res.status(400).json({ message: 'Invalid date format provided' }); + } + + // Create the transaction with user-confirmed data + const newTransaction = new IncomeExpense({ + user: req.user.id, + name: transactionData.name, + category: transactionData.category, + cost: transactionData.cost, + addedOn: transactionDate, + isIncome: transactionData.isIncome || false, + }); + + const savedTransaction = await newTransaction.save(); + + // Update the receipt with the final confirmed data + receipt.extractedData = { + merchant: transactionData.name, + amount: transactionData.cost, + category: transactionData.category, + date: transactionDate, + isIncome: transactionData.isIncome || false, + }; + await receipt.save(); + + res.status(201).json({ + message: 'Transaction saved successfully', + transaction: savedTransaction, + receipt: receipt + }); + + } catch (error) { + console.error('Error saving transaction:', error); + res.status(500).json({ message: 'Failed to save transaction', error: error.message }); + } +}; + module.exports = { uploadReceipt, + saveTransactionFromReceipt, }; diff --git a/backend/routes/receiptRoutes.js b/backend/routes/receiptRoutes.js index 8e192d3..c9849b3 100644 --- a/backend/routes/receiptRoutes.js +++ b/backend/routes/receiptRoutes.js @@ -1,9 +1,10 @@ const express = require('express'); const router = express.Router(); -const { uploadReceipt } = require('../controllers/receiptController'); +const { uploadReceipt, saveTransactionFromReceipt } = require('../controllers/receiptController'); const { protect } = require('../middleware/authMiddleware'); const upload = require('../middleware/uploadMiddleware'); router.post('/upload', protect, upload, uploadReceipt); +router.post('/save-transaction', protect, saveTransactionFromReceipt); module.exports = router; \ No newline at end of file diff --git a/frontend/src/pages/ReceiptsPage.jsx b/frontend/src/pages/ReceiptsPage.jsx index ef72ccb..7082bf9 100644 --- a/frontend/src/pages/ReceiptsPage.jsx +++ b/frontend/src/pages/ReceiptsPage.jsx @@ -1,148 +1,291 @@ import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import api from "../api/axios"; +import TransactionModal from "../components/TransactionModal"; import { toast, Bounce } from "react-toastify"; + const ReceiptsPage = () => { - const [isMobile, setIsMobile] = useState(window.innerWidth <= 767); - const [file, setFile] = useState(null); - const [uploading, setUploading] = useState(false); - const [receiptResult, setReceiptResult] = useState(null); - const [error, setError] = useState(""); - const navigate = useNavigate(); - - useEffect(() => { - const handleResize = () => { - return setIsMobile(window.innerWidth <= 767); - }; - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); - - const handleFileChange = (e) => { - setFile(e.target.files[0]); - setReceiptResult(null); - setError(""); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - if (!file) { - setError("Please select a file to upload."); - return; - } - - const formData = new FormData(); - formData.append("receipt", file); - - try { - setUploading(true); - setError(""); - const response = await api.post("/receipts/upload", formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }); - setReceiptResult(response.data); - - toast.success("Receipt processed successfully and transaction created!", { - position: "top-right", - autoClose: 3000, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - style: { - fontSize: "18px", // increases text size - padding: "16px 24px", // increases toast size - minWidth: "500px", // optional: increase width - }, - theme: "light", - transition: Bounce, - }); - } catch (err) { - setError("Upload failed. Please try again."); - console.error(err); - } finally { - setUploading(false); - } - }; - - return ( - <> -
- Merchant:{" "} - {receiptResult.extractedData.merchant} -
-- Amount:{" "} - {receiptResult.extractedData.amount.toFixed(2)} -
-- Category:{" "} - {receiptResult.extractedData.category} -
-- Date:{" "} - {new Date( - receiptResult.extractedData.date - ).toLocaleDateString()} -
-- Upload a receipt to see the extracted data here. -
- )} -+ Merchant:{" "} + {receiptResult.extractedData.merchant} +
++ Amount:{" "} + {( + parseFloat( + receiptResult.extractedData.amount + ) || 0 + ).toFixed(2)} +
++ Category:{" "} + {receiptResult.extractedData.category} +
++ Date:{" "} + {new Date( + receiptResult.extractedData.date + ).toLocaleDateString()} +
+ {receiptResult.extractedData.isIncome && ( ++ Income: Yes +
+ )} ++ Upload a receipt to see the extracted data here. +
+ )} +