diff --git a/src/main/java/com/example/FinanceTracker/Components/PdfManager.java b/src/main/java/com/example/FinanceTracker/Components/PdfManager.java index 2502298..5ecaf62 100644 --- a/src/main/java/com/example/FinanceTracker/Components/PdfManager.java +++ b/src/main/java/com/example/FinanceTracker/Components/PdfManager.java @@ -34,7 +34,7 @@ public class PdfManager { public List extractTransactions() { PagePdfDocumentReader pdfReader = - new PagePdfDocumentReader("/static/statements/account_statement_1-Jan-2024_to_2-Feb-2026.pdf"); + new PagePdfDocumentReader("/static/statements/statement.pdf"); List documents = pdfReader.get(); StringBuilder documentText = new StringBuilder(); diff --git a/src/main/java/com/example/FinanceTracker/Controllers/AddTransaction.java b/src/main/java/com/example/FinanceTracker/Controllers/AddTransaction.java index 4bbfc37..03a8fef 100644 --- a/src/main/java/com/example/FinanceTracker/Controllers/AddTransaction.java +++ b/src/main/java/com/example/FinanceTracker/Controllers/AddTransaction.java @@ -1,5 +1,10 @@ package com.example.FinanceTracker.Controllers; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; @@ -11,6 +16,11 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import com.example.FinanceTracker.Components.PdfManager; import com.example.FinanceTracker.Models.Transaction; import com.example.FinanceTracker.Repo.CategoriesRepo; import com.example.FinanceTracker.Services.TransactionService; @@ -24,44 +34,68 @@ public class AddTransaction { @Autowired private CategoriesRepo categoriesRepo; + @Autowired + private PdfManager pdfManager; + + // Define the upload directory + private static final String UPLOAD_DIR = "src/main/resources/static/statements/"; + @GetMapping("/addTransaction") public String addTransaction(Model model) { - model.addAttribute("transaction", new Transaction()); - List categoriesNames = categoriesRepo.findAllCategoryNames(); model.addAttribute("categories", categoriesNames); - - // footer model.addAttribute("footerDetails", "Copyright © FinanceTracker " + LocalDate.now().getYear()); - return "add-transaction"; } @PostMapping("/addTranaction") public String addNewTranactions(@ModelAttribute Transaction transaction, Model model) { - transactionValidation(transaction); - transactionService.save(transaction); - - // Set Category Dropdown menu List categoriesNames = categoriesRepo.findAllCategoryNames(); model.addAttribute("categories", categoriesNames); - return "add-transaction"; + } + @PostMapping("/uploadStatement") + public String uploadStatement(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { + if (file.isEmpty() || !file.getOriginalFilename().endsWith(".pdf")) { + redirectAttributes.addFlashAttribute("message", "Please select a valid PDF file."); + return "redirect:/addTransaction"; + } + + try { + // Ensure directory exists + Path uploadPath = Paths.get(UPLOAD_DIR); + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + // Resolve file path and save (Replace existing) + Path filePath = uploadPath.resolve("statement.pdf"); + Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); + + redirectAttributes.addFlashAttribute("message", + "File uploaded successfully: " + file.getOriginalFilename()); + + + transactionService.saveAll(pdfManager.extractTransactions()); + + } catch (IOException e) { + e.printStackTrace(); + redirectAttributes.addFlashAttribute("message", "Failed to upload file."); + } + + return "redirect:/addTransaction"; } private void transactionValidation(Transaction transaction) { - - // Set Balance BigDecimal balance = new BigDecimal(transactionService.getLatestBalance()); transaction.setBalance( balance.add(transaction.getMoneyIn()) .add(transaction.getFee()).setScale(2, RoundingMode.HALF_UP)); - // Set Money In/Money Out if (transaction.getMoneyIn().compareTo(BigDecimal.ZERO) > 0) { transaction.setMoneyOut(new BigDecimal(0)); } else if ((transaction.getMoneyIn().compareTo(BigDecimal.ZERO) < 0)) { @@ -69,11 +103,8 @@ public class AddTransaction { transaction.setMoneyIn(new BigDecimal(0)); } - // Handle Fees if (transaction.getFee() == null) { transaction.setFee(new BigDecimal(0)); } - } - -} +} \ No newline at end of file diff --git a/src/main/java/com/example/FinanceTracker/Controllers/HomeController.java b/src/main/java/com/example/FinanceTracker/Controllers/HomeController.java index b0fd03f..585dacf 100644 --- a/src/main/java/com/example/FinanceTracker/Controllers/HomeController.java +++ b/src/main/java/com/example/FinanceTracker/Controllers/HomeController.java @@ -16,7 +16,6 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.example.FinanceTracker.Components.BootstrapColours; -//import com.example.FinanceTracker.Components.PdfManager; import com.example.FinanceTracker.DTOs.GroupedTransaction; import com.example.FinanceTracker.DTOs.Home.OverviewCard; import com.example.FinanceTracker.Models.Transaction; @@ -25,10 +24,6 @@ import com.example.FinanceTracker.Services.TransactionService; @Controller public class HomeController { - -// @Autowired -// private PdfManager pdfManager; - @Autowired private TransactionService transactionsService; @@ -41,8 +36,6 @@ public class HomeController { @GetMapping("/") public String Home(Model model) { - //transactionsService.saveAll(pdfManager.extractTransactions()); - // Balance Text model.addAttribute("latestBalance", "Balance: " + currencyFormat.format(transactionsService.getLatestBalance()) + "*"); diff --git a/src/main/java/com/example/FinanceTracker/Controllers/InvestmentsController.java b/src/main/java/com/example/FinanceTracker/Controllers/InvestmentsController.java index b4581e2..641d8fc 100644 --- a/src/main/java/com/example/FinanceTracker/Controllers/InvestmentsController.java +++ b/src/main/java/com/example/FinanceTracker/Controllers/InvestmentsController.java @@ -3,6 +3,7 @@ package com.example.FinanceTracker.Controllers; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -10,6 +11,7 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; + import com.example.FinanceTracker.DTOs.Investments.CurrRate; import com.example.FinanceTracker.DTOs.Investments.Currency; import com.example.FinanceTracker.DTOs.Investments.ExchangeRate; @@ -27,66 +29,53 @@ public class InvestmentsController { @GetMapping("/investments") public String investments(Model model) { - - // Heading - model.addAttribute("latestBalance", "Total Invested: " +transactionService.getTotalInvested() + "*"); - - // Create Currency Pills - List allRates = investmentService.getCurrRates(); - model.addAttribute("currRates", allRates); - - // Populate Historical Rates Dropdown - List allCurrencies = investmentService.getAllCurrencies(); - model.addAttribute("allCurrencies", allCurrencies); - - // historical rates chart - List historicalExchangeRates = investmentService.getHistoricalRates("USD", 1).reversed(); - - // Get all the rates from the historical Exchange Rates - ArrayList historicalRates = new ArrayList<>(); - ArrayList historicalRatesLabels = new ArrayList<>(); - for (ExchangeRate exchangeRate : historicalExchangeRates) { - historicalRates.add(exchangeRate.getRate()); - historicalRatesLabels.add(exchangeRate.getCreatedAt().toLocalDate()); - } - - model.addAttribute("historicalRates", historicalRates); - model.addAttribute("labels", historicalRatesLabels); - - // footer + // Stats and Header + model.addAttribute("latestBalance", "Total Invested: " + transactionService.getTotalInvested() + "*"); + + // Navigation / UI Elements + model.addAttribute("currRates", investmentService.getCurrRates()); + model.addAttribute("allCurrencies", investmentService.getAllCurrencies()); model.addAttribute("footerDetails", "Copyright © FinanceTracker " + LocalDate.now().getYear()); + // Default Chart Data: USD for 1 Week + populateChartModel("USD", "WEEK", model); + return "investments"; } @GetMapping("/historicalRates") public String getHistoricalRates( - @RequestParam("duration") String durationCode, - @RequestParam("currencyChange") String currencyChange, + @RequestParam(value = "duration", defaultValue = "WEEK") String durationCode, + @RequestParam(value = "currencyChange", defaultValue = "USD") String currencyChange, Model model) { - // historical rates chart - int duration = 1; - if (durationCode.equals("MONTH")) - duration = 2; - if (durationCode.equals("YEAR")) - duration = 3; - - List historicalExchangeRates = investmentService.getHistoricalRates(currencyChange, duration) - .reversed(); - - // Get all the rates from the historical Exchange Rates - ArrayList historicalRates = new ArrayList<>(); - ArrayList historicalRatesLabels = new ArrayList<>(); - for (ExchangeRate exchangeRate : historicalExchangeRates) { - historicalRates.add(exchangeRate.getRate()); - historicalRatesLabels.add(exchangeRate.getCreatedAt().toLocalDate()); - } - - model.addAttribute("historicalRates", historicalRates); - model.addAttribute("labels", historicalRatesLabels); + populateChartModel(currencyChange, durationCode, model); + // Return only the fragment for HTMX to inject return "investments :: HistoricalRatesChart"; } -} + /** + * Helper to process business logic for chart data + */ + private void populateChartModel(String currencyCode, String durationCode, Model model) { + int durationKey = 1; + if ("MONTH".equals(durationCode)) durationKey = 2; + if ("YEAR".equals(durationCode)) durationKey = 3; + + // Fetch rates and ensure they are sorted by date ascending for the chart + List ratesList = investmentService.getHistoricalRates(currencyCode, durationKey); + Collections.reverse(ratesList); + + List data = new ArrayList<>(); + List labels = new ArrayList<>(); + + for (ExchangeRate er : ratesList) { + data.add(er.getRate()); + labels.add(er.getCreatedAt().toLocalDate()); + } + + model.addAttribute("historicalRates", data); + model.addAttribute("labels", labels); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/FinanceTracker/Controllers/TransactionsController.java b/src/main/java/com/example/FinanceTracker/Controllers/TransactionsController.java index a11b320..aaee8e9 100644 --- a/src/main/java/com/example/FinanceTracker/Controllers/TransactionsController.java +++ b/src/main/java/com/example/FinanceTracker/Controllers/TransactionsController.java @@ -1,102 +1,58 @@ package com.example.FinanceTracker.Controllers; import java.time.LocalDate; -import java.util.Collection; -import java.util.List; -import java.util.Map; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - import com.example.FinanceTracker.Models.Transaction; import com.example.FinanceTracker.Services.TransactionService; @Controller public class TransactionsController { - private int pageNum = 0; - private int pageSize = 10; - @Autowired - private TransactionService transactionService; + @Autowired + private TransactionService transactionService; - @GetMapping("/transactions") - public String transactions(Model model) { + // Main Page Load + @GetMapping("/transactions") + public String transactions( + Model model, + @RequestParam(defaultValue = "0") int pageNum, + @RequestParam(defaultValue = "10") int pageSize) { - Page pageResults = transactionService.findAllPaged(pageNum, pageSize); + prepareModel(model, "", pageNum, pageSize); + model.addAttribute("footerDetails", "Copyright © FinanceTracker " + LocalDate.now().getYear()); + return "transactions"; + } - model.addAttribute("pageNum", pageNum); - model.addAttribute("pageSize", pageResults.getTotalPages()); - model.addAttribute("transactionCount", pageResults.getNumberOfElements()); - model.addAttribute("transactions", pageResults); + @GetMapping({ "/transactions-fragment", "/searchTransactions" }) + public String getTransactionsFragment( + Model model, + @RequestParam(value = "searchValue", required = false, defaultValue = "") String searchValue, + @RequestParam(defaultValue = "0") int pageNum, + @RequestParam(defaultValue = "10") int pageSize) { - // footer - model.addAttribute("footerDetails", "Copyright © FinanceTracker " + LocalDate.now().getYear()); + prepareModel(model, searchValue, pageNum, pageSize); + // Returns only the specific fragment inside transactions.html + return "transactions :: transactionList"; + } - return "transactions"; + private void prepareModel(Model model, String search, int page, int size) { + Page results; + if (search == null || search.isBlank()) { + results = transactionService.findAllPaged(page, size); + } else { + results = transactionService.searchTransactions(search, page, size); } - @GetMapping("/changePageSize") - public Collection changePageSize( - @RequestParam("pageSizeCB") int pageSizeCB) { - this.pageSize = pageSizeCB; - this.pageNum = 0; - - Page results = transactionService.findAllPaged(pageNum, pageSize); - return List.of( - new ModelAndView("transactions :: transactionTable", - Map.of("transactions", results)), - setPageDetails(results)); - } - - @GetMapping("/searchTransactions") - public Collection SearchTable(@RequestParam("searchValue") String searchValue) { - this.pageNum = 0; - Page searchResults = transactionService.searchTransactions(searchValue, pageNum, pageSize); - - return List.of( - new ModelAndView("transactions :: transactionTable", - Map.of("transactions", searchResults)), - setPageDetails(searchResults)); - } - - @GetMapping("/previousPage") - public Collection previousPage() { - - if (pageNum > 0) { - pageNum--; - } - Page results = transactionService.findAllPaged(pageNum, pageSize); - return List.of( - new ModelAndView("transactions :: transactionTable", - Map.of("transactions", results)), - setPageDetails(results)); - } - - @GetMapping("/nextPage") - public Collection nextPage() { - - Page current = transactionService.findAllPaged(pageNum, pageSize); - - if (pageNum < current.getTotalPages() - 1) { - pageNum++; - } - Page results = transactionService.findAllPaged(pageNum, pageSize); - return List.of( - new ModelAndView("transactions :: transactionTable", - Map.of("transactions", results)), - setPageDetails(results)); - } - - private ModelAndView setPageDetails(Page page) { - return new ModelAndView("transactions :: tableInfo", - Map.of("pageNum", pageNum, "pageSize", page.getTotalPages(), "transactionCount", - page.getNumberOfElements())); - - } + model.addAttribute("transactions", results.getContent()); + model.addAttribute("pageNum", page); + model.addAttribute("totalPages", results.getTotalPages()); + model.addAttribute("searchValue", search); + model.addAttribute("pageSize", size); + } } diff --git a/src/main/java/com/example/FinanceTracker/Repo/TransactionRepo.java b/src/main/java/com/example/FinanceTracker/Repo/TransactionRepo.java index 567327e..edc84fa 100644 --- a/src/main/java/com/example/FinanceTracker/Repo/TransactionRepo.java +++ b/src/main/java/com/example/FinanceTracker/Repo/TransactionRepo.java @@ -19,8 +19,9 @@ import com.example.FinanceTracker.Models.Transaction; public interface TransactionRepo extends JpaRepository { @Query(value = """ - SELECT * - FROM transactions + SELECT t.balance, t.date, t.money_in, t.money_out, t.description, c.category_name as category, t.fee, t.id + FROM transactions t + JOIN categories c ON t.category = c.id WHERE date BETWEEN :fromDate AND :toDate ORDER BY date """, nativeQuery = true) @@ -85,6 +86,7 @@ public interface TransactionRepo extends JpaRepository { FROM transactions t JOIN categories c ON t.category = c.id WHERE money_in != 0 GROUP BY category + ORDER BY SUM(money_in) DESC """, nativeQuery = true) List getIncomeGroupedTransactions(); @@ -110,6 +112,8 @@ public interface TransactionRepo extends JpaRepository { FROM transactions t JOIN categories c ON t.category = c.id WHERE money_out != 0 GROUP BY category + ORDER BY SUM(money_out) + LIMIT 10 """, nativeQuery = true) List getExpenditureGroupedTransactions(); diff --git a/src/main/resources/static/statements/account_statement.pdf b/src/main/resources/static/statements/account_statement.pdf deleted file mode 100644 index cae3a71..0000000 Binary files a/src/main/resources/static/statements/account_statement.pdf and /dev/null differ diff --git a/src/main/resources/static/statements/account_statement_1-Jan-2024_to_2-Feb-2026.pdf b/src/main/resources/static/statements/account_statement_1-Jan-2024_to_2-Feb-2026.pdf deleted file mode 100644 index a0571d9..0000000 Binary files a/src/main/resources/static/statements/account_statement_1-Jan-2024_to_2-Feb-2026.pdf and /dev/null differ diff --git a/src/main/resources/templates/add-transaction.html b/src/main/resources/templates/add-transaction.html index 48acbef..007a293 100644 --- a/src/main/resources/templates/add-transaction.html +++ b/src/main/resources/templates/add-transaction.html @@ -8,68 +8,19 @@ FinanceTracker | Add Transaction - @@ -83,9 +34,14 @@
+ +

Add Transaction

- @@ -161,23 +117,25 @@
-