diff --git a/JavaJPA.iml b/JavaJPA.iml new file mode 100644 index 00000000..51151a37 --- /dev/null +++ b/JavaJPA.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index d586b911..6e5f5df6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,8 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.example @@ -9,46 +10,101 @@ 1.0-SNAPSHOT - 25 + 17 UTF-8 - 6.0.1 - 3.27.6 - 5.21.0 + 21.0.1 + 6.4.1.Final + + - org.junit.jupiter - junit-jupiter - ${junit.jupiter.version} - test + org.openjfx + javafx-controls + ${javafx.version} - org.assertj - assertj-core - ${assertj.core.version} - test - - - org.mockito - mockito-junit-jupiter - ${mockito.version} - test + org.openjfx + javafx-fxml + ${javafx.version} + + org.hibernate.orm hibernate-core - 7.2.0.Final + ${hibernate.version} + + jakarta.persistence + jakarta.persistence-api + 3.1.0 + + + com.mysql mysql-connector-j - 9.5.0 - runtime + 9.1.0 + + - io.github.classgraph - classgraph - 4.8.184 + org.junit.jupiter + junit-jupiter-api + 5.10.1 + test + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 17 + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + org.example.Main + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + + + org.example.Main + + + META-INF/persistence.xml + + + + + + + + + + + + diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index f2f67ca3..c275f9cc 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -1,57 +1,54 @@ package org.example; import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.Persistence; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.example.util.DataInitializer; +import org.example.util.JPAUtil; + +public class App extends Application { +// ... existing code ... + + @Override + public void start(Stage stage) { + try { + // 1. Initiera databasen och ladda testdata (seed) + initDatabase(); + + // 2. Ladda gränssnittet + FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("/fxml/main_view.fxml")); + Scene scene = new Scene(fxmlLoader.load(), 1000, 600); + + // Koppla på CSS för "Hertz-looken" + // scene.getStylesheets().add(App.class.getResource("/css/main.css").toExternalForm()); + + stage.setTitle("Hertz Biluthyrning - Grupp 7"); + stage.setScene(scene); + stage.show(); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Kunde inte starta applikationen: " + e.getMessage()); + } + } -public class App { - public static void main(String[] args) { - EntityManagerFactory emf = Persistence.createEntityManagerFactory("carRentalPU"); - EntityManager em = emf.createEntityManager(); - - em.getTransaction().begin(); - - // Rensa tabellen innan vi lägger in nya bilar - em.createQuery("DELETE FROM Car").executeUpdate(); - - // Skapa och persist 20 bilar med rimliga dagspriser - em.persist(createCar("Volvo", "XC90", 2025, "Lyxbil", 800_000)); - em.persist(createCar("BMW", "7 Series", 2024, "Lyxbil", 1_000_000)); - em.persist(createCar("Audi", "A8", 2023, "Lyxbil", 900_000)); - em.persist(createCar("Toyota", "Corolla", 2022, "Personbil", 200_000)); - em.persist(createCar("Honda", "Civic", 2021, "Personbil", 190_000)); - em.persist(createCar("Ford", "Focus", 2022, "Personbil", 210_000)); - em.persist(createCar("Mercedes", "Sprinter", 2023, "Minibuss", 350_000)); - em.persist(createCar("Volkswagen", "Transporter", 2023, "Minibuss", 330_000)); - em.persist(createCar("Renault", "Kangoo", 2022, "Minibuss", 300_000)); - em.persist(createCar("Fiat", "500", 2023, "Småbil", 120_000)); - em.persist(createCar("Mini", "Cooper", 2022, "Småbil", 150_000)); - em.persist(createCar("Volkswagen", "Up!", 2022, "Småbil", 110_000)); - em.persist(createCar("Porsche", "911", 2025, "Sportbil", 1_500_000)); - em.persist(createCar("Ferrari", "Roma", 2024, "Sportbil", 2_000_000)); - em.persist(createCar("Lamborghini", "Huracan", 2025, "Sportbil", 2_500_000)); - em.persist(createCar("Tesla", "Model S", 2023, "Lyxbil", 900_000)); - em.persist(createCar("Kia", "Rio", 2022, "Småbil", 130_000)); - em.persist(createCar("Hyundai", "i30", 2023, "Personbil", 195_000)); - em.persist(createCar("Opel", "Vivaro", 2022, "Minibuss", 320_000)); - em.persist(createCar("Jaguar", "F-Type", 2024, "Sportbil", 1_600_000)); - - em.getTransaction().commit(); - em.close(); - emf.close(); - - System.out.println("20 cars saved successfully!"); + private void initDatabase() { + EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager(); + try { + DataInitializer.seed(em); + } finally { + em.close(); + } } - // Skapar bil och sätter dagspris till 0,5% av priset - private static Car createCar(String brand, String model, int year, String type, double price) { - Car car = new Car; - car.setBrand(brand); - car.setModel(model); - car.setYear(year); - car.setType(type); - car.setPrice(price); - car.setDailyPrice(price * 0.005); // 0,5% av totalpris - return car; + @Override + public void stop() { + // Stäng ner JPA snyggt när appen stängs + JPAUtil.shutdown(); + } + + public static void main(String[] args) { + launch(); } } diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java new file mode 100644 index 00000000..6bf26dd2 --- /dev/null +++ b/src/main/java/org/example/Main.java @@ -0,0 +1,7 @@ +package org.example; + +public class Main { + public static void main(String[] args) { + App.main(args); + } +} diff --git a/src/main/java/org/example/controller/BookingController.java b/src/main/java/org/example/controller/BookingController.java index 7c7a57e5..9b300fa3 100644 --- a/src/main/java/org/example/controller/BookingController.java +++ b/src/main/java/org/example/controller/BookingController.java @@ -1,4 +1,139 @@ package org.example.controller; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import org.example.model.Car; +import org.example.model.Extra; +import org.example.util.JPAUtil; +import jakarta.persistence.EntityManager; +import java.util.ArrayList; +import java.util.List; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; + public class BookingController { + + @FXML private ComboBox carComboBox; + @FXML private DatePicker startDatePicker; + @FXML private DatePicker endDatePicker; + @FXML private TextField customerEmailField; + @FXML private VBox extrasContainer; + @FXML private Label daysLabel; + @FXML private Label carPriceLabel; + @FXML private Label totalAmountLabel; + + private List extraCheckBoxes = new ArrayList<>(); + + @FXML + public void initialize() { + loadExtras(); + loadAvailableCars(); // <--- Anropa den nya metoden här + } + + private void loadAvailableCars() { + EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager(); + try { + // Hämtar alla bilar som är markerade som tillgängliga + List availableCars = em.createQuery("SELECT c FROM Car c WHERE c.available = true", Car.class).getResultList(); + carComboBox.getItems().setAll(availableCars); + } finally { + em.close(); + } + } + + private void loadExtras() { + EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager(); + try { + List allExtras = em.createQuery("SELECT e FROM Extra e", Extra.class).getResultList(); + + extrasContainer.getChildren().clear(); + extraCheckBoxes.clear(); + + for (Extra extra : allExtras) { + CheckBox cb = new CheckBox(extra.getName() + " (" + extra.getPrice() + " kr)"); + cb.setUserData(extra); + cb.setOnAction(e -> updatePriceDisplay()); // Uppdatera totalpris när man klickar + extraCheckBoxes.add(cb); + extrasContainer.getChildren().add(cb); + } + } finally { + em.close(); + } + } + + @FXML + public void updatePriceDisplay() { + Car selectedCar = carComboBox.getSelectionModel().getSelectedItem(); + LocalDate start = startDatePicker.getValue(); + LocalDate end = endDatePicker.getValue(); + + long days = 0; + double carTotal = 0; + double extrasTotal = 0; + + // 1. Räkna ut antal dagar + if (start != null && end != null && !end.isBefore(start)) { + days = ChronoUnit.DAYS.between(start, end); + if (days == 0) days = 1; // Räkna påbörjad dag som 1 dag + } + + // 2. Räkna ut bilens pris + if (selectedCar != null) { + carTotal = selectedCar.getDailyPrice() * days; + } + + // 3. Räkna ut tilläggens pris + extrasTotal = extraCheckBoxes.stream() + .filter(CheckBox::isSelected) + .mapToDouble(cb -> ((Extra) cb.getUserData()).getPrice()) + .sum(); + + // 4. Uppdatera labels i UI + daysLabel.setText(String.valueOf(days)); + carPriceLabel.setText(String.format("%.2f kr", carTotal)); + + double total = carTotal + extrasTotal; + totalAmountLabel.setText(String.format("%.2f kr", total)); + } + + @FXML + public void onConfirmBooking() { + // Kontrollera att allt är ifyllt + if (carComboBox.getValue() == null || startDatePicker.getValue() == null || customerEmailField.getText().isEmpty()) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setContentText("Vänligen fyll i alla uppgifter!"); + alert.show(); + return; + } + + // HÄR SKER SJÄLVA SPARANDET I DATABASEN (Logik läggs till i BookingService sen) + + // Tack-meddelande + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Bokning Bekräftad"); + alert.setHeaderText("Tack för din bokning!"); + alert.setContentText("Din bil är nu reserverad. Välkommen att hämta den på valt startdatum."); + + alert.showAndWait(); + + // Rensa formuläret eller hoppa tillbaka till huvudvyn + clearForm(); + } + + private void clearForm() { + carComboBox.getSelectionModel().clearSelection(); + startDatePicker.setValue(null); + endDatePicker.setValue(null); + customerEmailField.clear(); + extraCheckBoxes.forEach(cb -> cb.setSelected(false)); + updatePriceDisplay(); + } + + public void setSelectedCar(Car car) { + if (carComboBox != null && car != null) { + carComboBox.getSelectionModel().select(car); + updatePriceDisplay(); + } + } } diff --git a/src/main/java/org/example/controller/CarController.java b/src/main/java/org/example/controller/CarController.java index 49de64a5..d9254192 100644 --- a/src/main/java/org/example/controller/CarController.java +++ b/src/main/java/org/example/controller/CarController.java @@ -1,11 +1,79 @@ package org.example.controller; +import javafx.beans.property.SimpleStringProperty; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.StackPane; +import javafx.beans.property.SimpleStringProperty; +import org.example.model.Car; +import org.example.model.CarType; +import org.example.service.CarService; +import java.io.IOException; + public class CarController { - boolean CarDamage=false; - boolean CarRented=false; - boolean CarAvailable=true; - boolean CarBooked=false; - boolean CarBroke=false; - boolean CarReturned=false; + @FXML private TableView carTable; + @FXML private TableColumn brandColumn; + @FXML private TableColumn modelColumn; + @FXML private TableColumn typeColumn; + @FXML + private TableColumn priceColumn; + @FXML + private TableColumn statusColumn; + @FXML + private TextField searchField; + + private final CarService carService = new CarService(); + + @FXML + public void initialize() { + // Koppla kolumnerna till Car-modellens fält via getters + brandColumn.setCellValueFactory(new PropertyValueFactory<>("brand")); + modelColumn.setCellValueFactory(new PropertyValueFactory<>("model")); + typeColumn.setCellValueFactory(new PropertyValueFactory<>("type")); + priceColumn.setCellValueFactory(new PropertyValueFactory<>("dailyPrice")); + + // Specialhantering för status-kolumnen för att visa ikoner/text istället för boolean + statusColumn.setCellValueFactory(cellData -> { + boolean available = cellData.getValue().isAvailable(); + boolean damaged = cellData.getValue().isDamaged(); + + if (damaged) return new SimpleStringProperty("🛠 Trasig"); + return new SimpleStringProperty(available ? "✅ Tillgänglig" : "❌ Uthyrd"); + }); + + loadCars(); + } + + private void loadCars() { + // Vi hämtar alla bilar för att visa hela vagnparken i tabellen + carTable.getItems().setAll(carService.getAllCars()); + } + + @FXML + public void handleBookingAction() { + Car selectedCar = carTable.getSelectionModel().getSelectedItem(); + if (selectedCar != null && selectedCar.isAvailable() && !selectedCar.isDamaged()) { + try { + // 1. Ladda bokningsvyn + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/booking_view.fxml")); + Parent bookingView = loader.load(); + + // 2. Skicka med den valda bilen till BookingController (valfritt men bra!) + BookingController bookingController = loader.getController(); + bookingController.setSelectedCar(selectedCar); + + // 3. Hitta contentArea och byt ut innehållet + StackPane contentArea = (StackPane) carTable.getScene().lookup("#contentArea"); + if (contentArea != null) { + contentArea.getChildren().setAll(bookingView); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } } diff --git a/src/main/java/org/example/controller/CustomerController.java b/src/main/java/org/example/controller/CustomerController.java index 6cbcc5d9..59c63ef6 100644 --- a/src/main/java/org/example/controller/CustomerController.java +++ b/src/main/java/org/example/controller/CustomerController.java @@ -1,5 +1,31 @@ package org.example.controller; -public interface CustomerController { - int addCustomer(); +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import org.example.model.Customer; +import org.example.service.CustomerService; + +public class CustomerController { + + @FXML + private TextField firstNameField; + @FXML + private TextField lastNameField; + @FXML + private TextField emailField; + + private final CustomerService customerService = new CustomerService(); + + @FXML + public void handleAddCustomer() { + String firstName = firstNameField.getText(); + String lastName = lastNameField.getText(); + String email = emailField.getText(); + + // Nu matchar vi konstruktorn Customer(String, String, String) + Customer customer = new Customer(firstName, lastName, email); + customerService.saveCustomer(customer); + + System.out.println("Kund sparad: " + firstName + " " + lastName); + } } diff --git a/src/main/java/org/example/controller/MainViewController.java b/src/main/java/org/example/controller/MainViewController.java index aa1a0cdc..6b188b2d 100644 --- a/src/main/java/org/example/controller/MainViewController.java +++ b/src/main/java/org/example/controller/MainViewController.java @@ -1,4 +1,40 @@ package org.example.controller; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.layout.StackPane; +import java.io.IOException; + public class MainViewController { + + @FXML + private StackPane contentArea; + + @FXML + public void showCarView() { + loadView("/fxml/car_view.fxml"); + } + + @FXML + public void showBookingView() { + loadView("/fxml/booking_view.fxml"); + } + + @FXML + public void showCustomerView() { + loadView("/fxml/customer_view.fxml"); + } + + private void loadView(String fxmlPath) { + try { + Parent view = FXMLLoader.load(getClass().getResource(fxmlPath)); + contentArea.getChildren().setAll(view); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Kunde inte ladda vyn: " + fxmlPath); + } + } } + + diff --git a/src/main/java/org/example/model/Booking.java b/src/main/java/org/example/model/Booking.java index 52fd6bb4..e64a340f 100644 --- a/src/main/java/org/example/model/Booking.java +++ b/src/main/java/org/example/model/Booking.java @@ -1,58 +1,52 @@ package org.example.model; import jakarta.persistence.*; - - -import javax.tools.DocumentationTool; -import javax.xml.stream.Location; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity public class Booking { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - - @ManyToOne(optional = false) - @JoinColumn(name = "customer_id") - Customer customer; - - @ManyToOne(optional = false) - @JoinColumn(name = "car_id") - private Car car; + private LocalDate startDate; + private LocalDate endDate; + @Enumerated(EnumType.STRING) + private BookingStatus status; @ManyToOne - @JoinColumn(name = "pickup_location_id") - private Location pickupLocation; + private Car car; @ManyToOne - @JoinColumn(name = "dropoff_location_id") - private Location dropoffLocation; - - @OneToMany - @JoinColumn(name = "booking_id") - private List extras; + private Customer customer; - @OneToOne - @JoinColumn(name = "payment_id") - private Payment payment; + @ManyToMany + private List extras = new ArrayList<>(); - private LocalDate startDate; - private LocalDate endDate; - - protected Booking() { - // JPA requires a no-arg constructor - } - - public Booking(Customer customer, Car car, Location pickupLocation, Location dropoffLocation, LocalDate startDate, LocalDate endDate) { + public Booking(Customer customer, Car car, LocalDate startDate, LocalDate endDate) { this.customer = customer; this.car = car; this.startDate = startDate; this.endDate = endDate; this.status = BookingStatus.RESERVED; - this.createdAt = LocalDateTime.now(); + } + + public void addExtra(Extra extra) { + this.extras.add(extra); + } + public long getDurationInDays() { + if (startDate == null || endDate == null) return 0; + return java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate); } + // Getters och Setters + public Long getId() { return id; } + public Customer getCustomer() { return customer; } + public Car getCar() { return car; } + public LocalDate getStartDate() { return startDate; } + public LocalDate getEndDate() { return endDate; } + public List getExtras() { return extras; } + public void setStatus(BookingStatus status) { this.status = status; } } diff --git a/src/main/java/org/example/model/Car.java b/src/main/java/org/example/model/Car.java index b5ec40f0..5aa518c8 100644 --- a/src/main/java/org/example/model/Car.java +++ b/src/main/java/org/example/model/Car.java @@ -1,12 +1,12 @@ -package org.example; +package org.example.model; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; @Entity public class Car { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -14,28 +14,41 @@ public class Car { private String brand; private String model; private int year; - private String type; - private double price; - private double dailyPrice; // ny egenskap - // Getters och setters + @Enumerated(EnumType.STRING) + private CarType type; + private double dailyPrice; + private boolean available = true; + private boolean damaged = false; + + public Car() {} + public Car(String brand, String model, int year, CarType type, double dailyPrice) { + this.brand = brand; + this.model = model; + this.year = year; + this.type = type; + this.dailyPrice = dailyPrice; + } + + // Getters som krävs för TableView (PropertyValueFactory) public Long getId() { return id; } - public String getBrand() { return brand; } - public void setBrand(String brand) { this.brand = brand; } - public String getModel() { return model; } - public void setModel(String model) { this.model = model; } - public int getYear() { return year; } - public void setYear(int year) { this.year = year; } - - public String getType() { return type; } - public void setType(String type) { this.type = type; } + public CarType getType() { return type; } + public double getDailyPrice() { return dailyPrice; } + public boolean isAvailable() { return available; } + public boolean isDamaged() { return damaged; } - public double getPrice() { return price; } - public void setPrice(double price) { this.price = price; } + // Setters + public void setAvailable(boolean available) { this.available = available; } + public void setDamaged(boolean damaged) { this.damaged = damaged; } - public double getDailyPrice() { return dailyPrice; } - public void setDailyPrice(double dailyPrice) { this.dailyPrice = dailyPrice; } + @Override + public String toString() { + return brand + " " + model + " (" + year + ") - " + dailyPrice + " kr/dag"; + } } + + + diff --git a/src/main/java/org/example/model/CarType.java b/src/main/java/org/example/model/CarType.java new file mode 100644 index 00000000..8ff7a84b --- /dev/null +++ b/src/main/java/org/example/model/CarType.java @@ -0,0 +1,9 @@ +package org.example.model; + +public enum CarType { + SMALL, + FAMILY, + VAN, + SPORT, + LUXURY +} diff --git a/src/main/java/org/example/model/Customer.java b/src/main/java/org/example/model/Customer.java index 1a66e22d..79ed55d3 100644 --- a/src/main/java/org/example/model/Customer.java +++ b/src/main/java/org/example/model/Customer.java @@ -8,14 +8,64 @@ public class Customer { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String name; + private String firstName; + private String lastName; private String email; + // -------- One-to-Many -------- @OneToMany(mappedBy = "customer") private List bookings = new ArrayList<>(); - // getters & setters + // -------- Constructors -------- + protected Customer() { + // JPA kräver tom konstruktor + } + + public Customer(String firstName, String lastName, String email) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } + + // -------- Getters & setters -------- + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getBookings() { + return bookings; + } + + // -------- Nice to have -------- + @Override + public String toString() { + return firstName + " " + lastName + " (" + email + ")"; + } } diff --git a/src/main/java/org/example/model/Extra.java b/src/main/java/org/example/model/Extra.java new file mode 100644 index 00000000..016a8fb7 --- /dev/null +++ b/src/main/java/org/example/model/Extra.java @@ -0,0 +1,31 @@ +package org.example.model; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class Extra { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private double price; + + @ManyToMany(mappedBy = "extras") + private List bookings = new ArrayList<>(); + + public Extra() {} + + public Extra(String name, double price) { + this.name = name; + this.price = price; + } + + public double getPrice() { return price; } + public void setPrice(double price) { this.price = price; } + public String getName() { return name; } +} + diff --git a/src/main/java/org/example/model/Location.java b/src/main/java/org/example/model/Location.java new file mode 100644 index 00000000..24f1e81a --- /dev/null +++ b/src/main/java/org/example/model/Location.java @@ -0,0 +1,24 @@ +package org.example.model; + +import jakarta.persistence.*; + +@Entity +public class Location { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + protected Location() {} + + public Location(String name) { + this.name = name; + } + + public Long getId() { return id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } +} + diff --git a/src/main/java/org/example/model/Payment.java b/src/main/java/org/example/model/Payment.java index 0e4a6dd0..e543d069 100644 --- a/src/main/java/org/example/model/Payment.java +++ b/src/main/java/org/example/model/Payment.java @@ -1,7 +1,27 @@ package org.example.model; import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; @Entity public class Payment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private double amount; + private String method; + + // getters och setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public double getAmount() { return amount; } + public void setAmount(double amount) { this.amount = amount; } + + public String getMethod() { return method; } + public void setMethod(String method) { this.method = method; } } diff --git a/src/main/java/org/example/repository/BookingRepository.java b/src/main/java/org/example/repository/BookingRepository.java index a63b50d4..6baa468e 100644 --- a/src/main/java/org/example/repository/BookingRepository.java +++ b/src/main/java/org/example/repository/BookingRepository.java @@ -1,4 +1,33 @@ package org.example.repository; +import org.example.model.Booking; +import jakarta.persistence.EntityManager; +import org.example.util.JPAUtil; +import java.util.List; + public class BookingRepository { + + public void save(Booking booking) { + EntityManager em = JPAUtil.getEntityManager(); + try { + em.getTransaction().begin(); + if (booking.getId() == null) { + em.persist(booking); + } else { + em.merge(booking); + } + em.getTransaction().commit(); + } catch (Exception e) { + if (em.getTransaction().isActive()) em.getTransaction().rollback(); + throw e; + } finally { + em.close(); + } + } + + public List findAll() { + try (EntityManager em = JPAUtil.getEntityManager()) { + return em.createQuery("SELECT b FROM Booking b", Booking.class).getResultList(); + } + } } diff --git a/src/main/java/org/example/repository/CarRepository.java b/src/main/java/org/example/repository/CarRepository.java index cff21c7b..77237737 100644 --- a/src/main/java/org/example/repository/CarRepository.java +++ b/src/main/java/org/example/repository/CarRepository.java @@ -1,21 +1,37 @@ package org.example.repository; import org.example.model.Car; -import org.example.util.JPAUtil; - import jakarta.persistence.EntityManager; +import org.example.util.JPAUtil; import java.util.List; public class CarRepository { public List findAll() { - EntityManager em = JPAUtil.getEntityManager(); + try (EntityManager em = JPAUtil.getEntityManager()) { + return em.createQuery("SELECT c FROM Car c", Car.class).getResultList(); + } + } - List cars = - em.createQuery("SELECT c FROM Car c", Car.class) - .getResultList(); + public List findAvailableCars() { + try (EntityManager em = JPAUtil.getEntityManager()) { + return em.createQuery("SELECT c FROM Car c WHERE c.available = true AND c.damaged = false", Car.class) + .getResultList(); + } + } - em.close(); - return cars; + public void save(Car car) { + EntityManager em = JPAUtil.getEntityManager(); + try { + em.getTransaction().begin(); + if (car.getId() == null) { + em.persist(car); + } else { + em.merge(car); + } + em.getTransaction().commit(); + } finally { + em.close(); + } } } diff --git a/src/main/java/org/example/repository/CustomerRepository.java b/src/main/java/org/example/repository/CustomerRepository.java index 38b788c2..36c35862 100644 --- a/src/main/java/org/example/repository/CustomerRepository.java +++ b/src/main/java/org/example/repository/CustomerRepository.java @@ -1,4 +1,41 @@ + package org.example.repository; +import org.example.model.Customer; +import jakarta.persistence.EntityManager; +import org.example.util.JPAUtil; +import java.util.List; +import java.util.Optional; + public class CustomerRepository { + + public void save(Customer customer) { + EntityManager em = JPAUtil.getEntityManager(); + try { + em.getTransaction().begin(); + if (customer.getId() == null) { + em.persist(customer); + } else { + em.merge(customer); + } + em.getTransaction().commit(); + } finally { + em.close(); + } + } + + public Optional findByEmail(String email) { + try (EntityManager em = JPAUtil.getEntityManager()) { + return em.createQuery("SELECT c FROM Customer c WHERE c.email = :email", Customer.class) + .setParameter("email", email) + .getResultStream() + .findFirst(); + } + } + + public List findAll() { + try (EntityManager em = JPAUtil.getEntityManager()) { + return em.createQuery("SELECT c FROM Customer c", Customer.class).getResultList(); + } + } } diff --git a/src/main/java/org/example/service/BookingService.java b/src/main/java/org/example/service/BookingService.java index aea99093..2957b158 100644 --- a/src/main/java/org/example/service/BookingService.java +++ b/src/main/java/org/example/service/BookingService.java @@ -1,4 +1,46 @@ package org.example.service; +import org.example.model.Booking; +import org.example.model.BookingStatus; +import org.example.model.Extra; +import org.example.repository.BookingRepository; +import org.example.repository.CarRepository; +import java.util.List; + public class BookingService { + + private final BookingRepository bookingRepository = new BookingRepository(); + private final CarRepository carRepository = new CarRepository(); + + public double calculateTotalPrice(Booking booking) { + if (booking.getCar() == null) return 0.0; + + long days = booking.getDurationInDays(); + if (days <= 0) days = 1; + + double carTotal = booking.getCar().getDailyPrice() * days; + double extrasTotal = booking.getExtras().stream() + .mapToDouble(Extra::getPrice) + .sum(); + + return carTotal + extrasTotal; + } + + public void createBooking(Booking booking) { + if (booking.getCar() == null || !booking.getCar().isAvailable()) { + throw new IllegalStateException("Bilen är inte tillgänglig för bokning."); + } + + // Sätt status och spara + booking.setStatus(BookingStatus.RESERVED); + bookingRepository.save(booking); + + // Uppdatera bilens status så den inte kan bokas av någon annan + booking.getCar().setAvailable(false); + carRepository.save(booking.getCar()); + } + + public List getAllBookings() { + return bookingRepository.findAll(); + } } diff --git a/src/main/java/org/example/service/CarService.java b/src/main/java/org/example/service/CarService.java index e81e1ea5..fee0ef7b 100644 --- a/src/main/java/org/example/service/CarService.java +++ b/src/main/java/org/example/service/CarService.java @@ -1,4 +1,29 @@ package org.example.service; +import org.example.model.Car; +import org.example.repository.CarRepository; +import java.util.List; + public class CarService { + + private final CarRepository carRepository = new CarRepository(); + + public List getAllCars() { + return carRepository.findAll(); + } + + public List getAvailableCars() { + return carRepository.findAvailableCars(); + } + + public void markAsDamaged(Car car) { + car.setDamaged(true); + car.setAvailable(false); + carRepository.save(car); + } + + public void updateCarStatus(Car car, boolean available) { + car.setAvailable(available); + carRepository.save(car); + } } diff --git a/src/main/java/org/example/service/CustomerService.java b/src/main/java/org/example/service/CustomerService.java index 4c00baea..436ff3a3 100644 --- a/src/main/java/org/example/service/CustomerService.java +++ b/src/main/java/org/example/service/CustomerService.java @@ -1,4 +1,29 @@ package org.example.service; +import org.example.model.Customer; +import org.example.repository.CustomerRepository; +import java.util.List; +import java.util.Optional; + public class CustomerService { + + private final CustomerRepository customerRepository = new CustomerRepository(); + + public void saveCustomer(Customer customer) { + // Validering: kolla om e-post redan finns + Optional existing = customerRepository.findByEmail(customer.getEmail()); + if (existing.isPresent()) { + System.out.println("Kunden finns redan i systemet."); + return; + } + customerRepository.save(customer); + } + + public List getAllCustomers() { + return customerRepository.findAll(); + } + + public Optional findCustomerByEmail(String email) { + return customerRepository.findByEmail(email); + } } diff --git a/src/main/java/org/example/util/DataInitializer.java b/src/main/java/org/example/util/DataInitializer.java new file mode 100644 index 00000000..9bdf24fe --- /dev/null +++ b/src/main/java/org/example/util/DataInitializer.java @@ -0,0 +1,53 @@ +package org.example.util; + +import jakarta.persistence.EntityManager; +import org.example.model.*; + +public class DataInitializer { + + public static void seed(EntityManager em) { + + + if (!em.createQuery("SELECT c FROM Car c").getResultList().isEmpty()) { + return; + } + + em.getTransaction().begin(); + + // ---------------- Cars (20 st) ---------------- + em.persist(new Car("Volkswagen", "Polo", 2022, CarType.SMALL, 399)); + em.persist(new Car("Toyota", "Yaris", 2023, CarType.SMALL, 429)); + em.persist(new Car("Hyundai", "i20", 2021, CarType.SMALL, 389)); + em.persist(new Car("Kia", "Rio", 2022, CarType.SMALL, 399)); + + em.persist(new Car("Volvo", "V60", 2023, CarType.FAMILY, 699)); + em.persist(new Car("Volkswagen", "Passat", 2022, CarType.FAMILY, 649)); + em.persist(new Car("Skoda", "Octavia", 2023, CarType.FAMILY, 629)); + em.persist(new Car("Toyota", "Corolla Touring", 2022, CarType.FAMILY, 619)); + + em.persist(new Car("Volkswagen", "Transporter", 2021, CarType.VAN, 899)); + em.persist(new Car("Ford", "Transit", 2022, CarType.VAN, 949)); + em.persist(new Car("Mercedes", "Vito", 2023, CarType.VAN, 999)); + em.persist(new Car("Renault", "Trafic", 2021, CarType.VAN, 879)); + + em.persist(new Car("BMW", "M3", 2023, CarType.SPORT, 1999)); + em.persist(new Car("Audi", "RS5", 2022, CarType.SPORT, 1899)); + em.persist(new Car("Porsche", "911", 2023, CarType.SPORT, 2999)); + em.persist(new Car("Mercedes", "AMG GT", 2022, CarType.SPORT, 2799)); + + em.persist(new Car("Mazda", "CX-5", 2022, CarType.FAMILY, 679)); + em.persist(new Car("Tesla", "Model Y", 2023, CarType.FAMILY, 799)); + em.persist(new Car("Mini", "Cooper", 2022, CarType.SMALL, 459)); + em.persist(new Car("Seat", "Leon", 2023, CarType.FAMILY, 599)); + + // ---------------- Extras ---------------- + em.persist(new Extra("Försäkring", 149)); + em.persist(new Extra("Takbox", 99)); + em.persist(new Extra("GPS", 59)); + em.persist(new Extra("Barnstol", 79)); + + em.getTransaction().commit(); + + System.out.println(" Seed-data skapad (20 bilar + extras)"); + } +} diff --git a/src/main/java/org/example/util/JPAUtil.java b/src/main/java/org/example/util/JPAUtil.java new file mode 100644 index 00000000..f5a56eb7 --- /dev/null +++ b/src/main/java/org/example/util/JPAUtil.java @@ -0,0 +1,24 @@ +package org.example.util; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.Persistence; + +public class JPAUtil { + private static final EntityManagerFactory emf = + Persistence.createEntityManagerFactory("carRentalPU"); + + public static EntityManagerFactory getEntityManagerFactory() { + return emf; + } + + public static EntityManager getEntityManager() { + return emf.createEntityManager(); + } + + public static void shutdown() { + if (emf != null && emf.isOpen()) { + emf.close(); + } + } +} diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index b43c6854..a7d6d15d 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -1,17 +1,34 @@ - - + + + + + org.hibernate.jpa.HibernatePersistenceProvider + + + org.example.model.Car + org.example.model.Customer + org.example.model.Booking + org.example.model.Extra + org.example.model.Location + org.example.model.Payment + + value="jdbc:mysql://127.0.0.1:3306/carrental?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"/> - - + + - + + + + + diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css index 90bc399a..342d5100 100644 --- a/src/main/resources/css/main.css +++ b/src/main/resources/css/main.css @@ -1,3 +1,130 @@ -tabell med bilar -uthyrnings tabell med many2man -kund tabell +/* Färgtema: Midnight Gold */ +.main-container { + -fx-background-color: #f4f7f6; +} + +.sidebar { + -fx-background-color: #1a2a3a; /* Mörk marinblå */ +} + +.logo-text { + -fx-text-fill: #ffcc00; /* Guld/Gul */ + -fx-margin-bottom: 20; +} + +.nav-button { + -fx-background-color: transparent; + -fx-text-fill: #ffffff; + -fx-alignment: CENTER_LEFT; + -fx-font-size: 14px; + -fx-cursor: hand; + -fx-padding: 10 15; + -fx-border-radius: 5; +} + +.nav-button:hover { + -fx-background-color: #2c3e50; + -fx-text-fill: #ffcc00; +} + +.welcome-header { + -fx-font-size: 32px; + -fx-font-weight: bold; + -fx-text-fill: #1a2a3a; +} + +.welcome-subtext { + -fx-font-size: 16px; + -fx-text-fill: #7f8c8d; +} + +.content-pane { + -fx-background-color: white; + -fx-background-radius: 10 0 0 10; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 0); +} + +.view-header { + -fx-font-size: 28px; + -fx-font-weight: bold; + -fx-text-fill: #1a2a3a; +} + +.view-subheader { + -fx-font-size: 14px; + -fx-text-fill: #7f8c8d; +} + +.search-field { + -fx-background-radius: 20; + -fx-padding: 8 15; + -fx-border-color: #dcdde1; + -fx-border-radius: 20; +} + +.modern-table { + -fx-background-color: transparent; + -fx-border-color: #dcdde1; + -fx-border-radius: 5; +} + +.modern-table .column-header { + -fx-background-color: #f8f9fa; + -fx-padding: 10; +} + +.primary-button { + -fx-background-color: #ffcc00; /* Guld */ + -fx-text-fill: #1a2a3a; + -fx-font-weight: bold; + -fx-padding: 10 25; + -fx-background-radius: 5; + -fx-cursor: hand; +} + +.primary-button:hover { + -fx-background-color: #e6b800; +} + +.secondary-button { + -fx-background-color: transparent; + -fx-border-color: #1a2a3a; + -fx-text-fill: #1a2a3a; + -fx-padding: 10 25; + -fx-background-radius: 5; + -fx-cursor: hand; +} + +.summary-pane { + -fx-background-color: #f8f9fa; + -fx-border-color: #dcdde1; + -fx-border-radius: 10; + -fx-background-radius: 10; +} + +.summary-header { + -fx-font-size: 18px; + -fx-font-weight: bold; + -fx-text-fill: #1a2a3a; +} + +.total-label { + -fx-font-weight: bold; + -fx-font-size: 14px; +} + +.total-price { + -fx-font-size: 22px; + -fx-font-weight: bold; + -fx-text-fill: #1a2a3a; +} + +.section-header { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-padding: 10 0; +} + +.bold-text { + -fx-font-weight: bold; +} diff --git a/src/main/resources/fxml/booking_view.fxml b/src/main/resources/fxml/booking_view.fxml index e69de29b..73ae9564 100644 --- a/src/main/resources/fxml/booking_view.fxml +++ b/src/main/resources/fxml/booking_view.fxml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + +