396 lines
11 KiB
Go
396 lines
11 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"tankstopp/internal/currency"
|
|
"tankstopp/internal/models"
|
|
"tankstopp/internal/views/pages"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// AddFuelStopHandler handles adding new fuel stops
|
|
func (h *Handler) AddFuelStopHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case "GET":
|
|
// Get user for default currency
|
|
user, err := h.db.GetUserByID(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting user: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Get vehicles for the user
|
|
vehicles, err := h.db.GetVehicles(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting vehicles: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Render add fuel stop form using templ
|
|
currencies := currency.SupportedCurrencies()
|
|
component := pages.AddFuelStopPage(user, user.Username, vehicles, currencies)
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
err = component.Render(r.Context(), w)
|
|
if err != nil {
|
|
log.Printf("Error rendering template: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
case "POST":
|
|
h.handleAddFuelStop(w, r, userID)
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// handleAddFuelStop processes the form submission for adding fuel stops
|
|
func (h *Handler) handleAddFuelStop(w http.ResponseWriter, r *http.Request, userID uint) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
log.Printf("Error parsing form: %v", err)
|
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Parse form data
|
|
form := models.FuelStopForm{
|
|
Date: strings.TrimSpace(r.FormValue("date")),
|
|
VehicleID: parseUint(r.FormValue("vehicle_id")),
|
|
StationName: strings.TrimSpace(r.FormValue("station_name")),
|
|
Location: strings.TrimSpace(r.FormValue("location")),
|
|
FuelType: r.FormValue("fuel_type"),
|
|
Liters: parseFloat(r.FormValue("amount")),
|
|
PricePerL: parseFloat(r.FormValue("price_per_liter")),
|
|
TotalPrice: parseFloat(r.FormValue("total_cost")),
|
|
Currency: r.FormValue("currency"),
|
|
Odometer: parseInt(r.FormValue("odometer")),
|
|
TripLength: parseFloat(r.FormValue("trip_length")),
|
|
Notes: r.FormValue("notes"),
|
|
}
|
|
|
|
// Validate form
|
|
if err := h.validateFuelStopForm(&form); err != nil {
|
|
log.Printf("Validation error: %v", err)
|
|
http.Redirect(w, r, "/add?error="+err.Error(), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Convert form to fuel stop
|
|
fuelStop, err := form.ToFuelStop(userID)
|
|
if err != nil {
|
|
log.Printf("Error converting form to fuel stop: %v", err)
|
|
http.Redirect(w, r, "/add?error=Invalid+date+format", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Use station name as location if location is empty
|
|
if fuelStop.Location == "" {
|
|
fuelStop.Location = fuelStop.StationName
|
|
}
|
|
|
|
// Save to database
|
|
err = h.db.CreateFuelStop(fuelStop)
|
|
if err != nil {
|
|
log.Printf("Error creating fuel stop: %v", err)
|
|
http.Redirect(w, r, "/add?error=Failed+to+save+fuel+stop", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Redirect to dashboard with success message
|
|
http.Redirect(w, r, "/dashboard?success=Fuel+stop+added+successfully", http.StatusSeeOther)
|
|
}
|
|
|
|
// EditFuelStopHandler handles editing existing fuel stops
|
|
func (h *Handler) EditFuelStopHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Get the user object
|
|
user, err := h.db.GetUserByID(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting user: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
idInt, err := strconv.Atoi(vars["id"])
|
|
if err != nil {
|
|
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
id := uint(idInt)
|
|
|
|
switch r.Method {
|
|
case "GET":
|
|
stop, err := h.db.GetFuelStopByID(id, userID)
|
|
if err != nil {
|
|
log.Printf("Error getting fuel stop: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if stop == nil {
|
|
http.Error(w, "Fuel stop not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Get vehicles for the user
|
|
vehicles, err := h.db.GetVehicles(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting vehicles: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Render edit fuel stop form using templ
|
|
currencies := currency.SupportedCurrencies()
|
|
component := pages.EditFuelStopPage(user, user.Username, stop, vehicles, currencies)
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
err = component.Render(r.Context(), w)
|
|
if err != nil {
|
|
log.Printf("Error rendering template: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
case "POST":
|
|
h.handleEditFuelStop(w, r, id, userID)
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// handleEditFuelStop processes the form submission for editing fuel stops
|
|
func (h *Handler) handleEditFuelStop(w http.ResponseWriter, r *http.Request, id, userID uint) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
log.Printf("Error parsing form: %v", err)
|
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get existing fuel stop
|
|
existingStop, err := h.db.GetFuelStopByID(id, userID)
|
|
if err != nil {
|
|
log.Printf("Error getting fuel stop: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if existingStop == nil {
|
|
http.Error(w, "Fuel stop not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Parse form data
|
|
form := models.FuelStopForm{
|
|
Date: strings.TrimSpace(r.FormValue("date")),
|
|
VehicleID: parseUint(r.FormValue("vehicle_id")),
|
|
StationName: strings.TrimSpace(r.FormValue("station_name")),
|
|
Location: strings.TrimSpace(r.FormValue("location")),
|
|
FuelType: r.FormValue("fuel_type"),
|
|
Liters: parseFloat(r.FormValue("amount")),
|
|
PricePerL: parseFloat(r.FormValue("price_per_liter")),
|
|
TotalPrice: parseFloat(r.FormValue("total_cost")),
|
|
Currency: r.FormValue("currency"),
|
|
Odometer: parseInt(r.FormValue("odometer")),
|
|
TripLength: parseFloat(r.FormValue("trip_length")),
|
|
Notes: r.FormValue("notes"),
|
|
}
|
|
|
|
// Validate form
|
|
if err := h.validateFuelStopForm(&form); err != nil {
|
|
log.Printf("Validation error: %v", err)
|
|
http.Redirect(w, r, fmt.Sprintf("/edit/%d?error=%s", id, err.Error()), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Convert form to fuel stop
|
|
updatedStop, err := form.ToFuelStop(userID)
|
|
if err != nil {
|
|
log.Printf("Error converting form to fuel stop: %v", err)
|
|
http.Redirect(w, r, fmt.Sprintf("/edit/%d?error=Invalid+date+format", id), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Set the ID to update existing record
|
|
updatedStop.ID = id
|
|
|
|
// Use station name as location if location is empty
|
|
if updatedStop.Location == "" {
|
|
updatedStop.Location = updatedStop.StationName
|
|
}
|
|
|
|
// Update in database
|
|
err = h.db.UpdateFuelStop(updatedStop)
|
|
if err != nil {
|
|
log.Printf("Error updating fuel stop: %v", err)
|
|
http.Redirect(w, r, fmt.Sprintf("/edit/%d?error=Failed+to+update+fuel+stop", id), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Redirect to dashboard with success message
|
|
http.Redirect(w, r, "/dashboard?success=Fuel+stop+updated+successfully", http.StatusSeeOther)
|
|
}
|
|
|
|
// DeleteFuelStopHandler handles deleting fuel stops
|
|
func (h *Handler) DeleteFuelStopHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
idInt, err := strconv.Atoi(vars["id"])
|
|
if err != nil {
|
|
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
id := uint(idInt)
|
|
|
|
// Verify fuel stop exists and belongs to user
|
|
fuelStop, err := h.db.GetFuelStopByID(id, userID)
|
|
if err != nil {
|
|
log.Printf("Error getting fuel stop: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if fuelStop == nil {
|
|
http.Error(w, "Fuel stop not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Delete fuel stop
|
|
err = h.db.DeleteFuelStop(id, userID)
|
|
if err != nil {
|
|
log.Printf("Error deleting fuel stop: %v", err)
|
|
http.Redirect(w, r, "/dashboard?error=Failed+to+delete+fuel+stop", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Redirect to dashboard with success message
|
|
http.Redirect(w, r, "/dashboard?success=Fuel+stop+deleted+successfully", http.StatusSeeOther)
|
|
}
|
|
|
|
// validateFuelStopForm validates the fuel stop form data
|
|
func (h *Handler) validateFuelStopForm(form *models.FuelStopForm) error {
|
|
if form.Date == "" {
|
|
return fmt.Errorf("Date is required")
|
|
}
|
|
|
|
// Validate date format
|
|
_, err := time.Parse("2006-01-02", form.Date)
|
|
if err != nil {
|
|
return fmt.Errorf("Invalid date format")
|
|
}
|
|
|
|
if form.VehicleID == 0 {
|
|
return fmt.Errorf("Vehicle is required")
|
|
}
|
|
|
|
if form.StationName == "" && form.Location == "" {
|
|
return fmt.Errorf("Station name or location is required")
|
|
}
|
|
|
|
if form.FuelType == "" {
|
|
return fmt.Errorf("Fuel type is required")
|
|
}
|
|
|
|
if form.Liters <= 0 {
|
|
return fmt.Errorf("Amount must be greater than 0")
|
|
}
|
|
|
|
if form.PricePerL <= 0 {
|
|
return fmt.Errorf("Price per liter must be greater than 0")
|
|
}
|
|
|
|
if form.TotalPrice <= 0 {
|
|
return fmt.Errorf("Total price must be greater than 0")
|
|
}
|
|
|
|
if form.Currency != "" && !currency.IsValidCurrency(form.Currency) {
|
|
return fmt.Errorf("Invalid currency")
|
|
}
|
|
|
|
if form.Odometer < 0 {
|
|
return fmt.Errorf("Odometer reading cannot be negative")
|
|
}
|
|
|
|
if form.TripLength < 0 {
|
|
return fmt.Errorf("Trip length cannot be negative")
|
|
}
|
|
|
|
if form.TripLength > 2000 {
|
|
return fmt.Errorf("Trip length cannot exceed 2000 km")
|
|
}
|
|
|
|
// Validate consumption if both trip length and amount are provided
|
|
if form.TripLength > 0 && form.Liters > 0 {
|
|
consumption := (form.Liters / form.TripLength) * 100
|
|
if consumption > 50 {
|
|
return fmt.Errorf("Fuel consumption %.1f L/100km seems unrealistic. Please check trip length and amount", consumption)
|
|
}
|
|
if consumption < 1 {
|
|
return fmt.Errorf("Fuel consumption %.1f L/100km seems too low. Please check trip length and amount", consumption)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper functions for parsing form values
|
|
func parseFloat(s string) float64 {
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return f
|
|
}
|
|
|
|
func parseInt(s string) int {
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return i
|
|
}
|
|
|
|
func parseUint(s string) uint {
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
i, err := strconv.ParseUint(s, 10, 32)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return uint(i)
|
|
}
|