Initialized Counter login and fixed bugs in the edit products option
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
ordering-site/*
|
ordering-site/*
|
||||||
ordering_site/
|
ordering_site/
|
||||||
counter/
|
counter-frontend/
|
||||||
@@ -7,8 +7,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/base-items")
|
@RequestMapping("/api/base-items")
|
||||||
public class BaseItemController {
|
public class BaseItemController {
|
||||||
@@ -18,8 +16,12 @@ public class BaseItemController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public org.springframework.data.domain.Page<BaseItem> getAllBaseItems(
|
public org.springframework.data.domain.Page<BaseItem> getAllBaseItems(
|
||||||
|
@RequestParam(required = false) String search,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@RequestParam(defaultValue = "10") int size) {
|
@RequestParam(defaultValue = "10") int size) {
|
||||||
|
if (search != null && !search.isEmpty()) {
|
||||||
|
return baseItemRepository.findByNameContainingIgnoreCase(search, org.springframework.data.domain.PageRequest.of(page, size));
|
||||||
|
}
|
||||||
return baseItemRepository.findAll(org.springframework.data.domain.PageRequest.of(page, size));
|
return baseItemRepository.findAll(org.springframework.data.domain.PageRequest.of(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public class OrderController {
|
|||||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate,
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate,
|
||||||
@RequestParam(required = false) String status,
|
@RequestParam(required = false) String status,
|
||||||
@RequestParam(required = false) String paymentType,
|
@RequestParam(required = false) String paymentType,
|
||||||
|
@RequestParam(required = false) String orderType,
|
||||||
@RequestParam(required = false) String search,
|
@RequestParam(required = false) String search,
|
||||||
@RequestParam(defaultValue = "false") boolean archived,
|
@RequestParam(defaultValue = "false") boolean archived,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@@ -66,6 +67,9 @@ public class OrderController {
|
|||||||
if (paymentType != null && !paymentType.isEmpty()) {
|
if (paymentType != null && !paymentType.isEmpty()) {
|
||||||
predicates.add(cb.equal(root.get("paymentMethod"), paymentType));
|
predicates.add(cb.equal(root.get("paymentMethod"), paymentType));
|
||||||
}
|
}
|
||||||
|
if (orderType != null && !orderType.isEmpty()) {
|
||||||
|
predicates.add(cb.equal(root.get("orderType"), orderType));
|
||||||
|
}
|
||||||
if (search != null && !search.isEmpty()) {
|
if (search != null && !search.isEmpty()) {
|
||||||
String searchLower = "%" + search.toLowerCase() + "%";
|
String searchLower = "%" + search.toLowerCase() + "%";
|
||||||
Join<Order, User> userJoin = root.join("user", JoinType.LEFT);
|
Join<Order, User> userJoin = root.join("user", JoinType.LEFT);
|
||||||
|
|||||||
@@ -18,8 +18,12 @@ public class ProductController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public org.springframework.data.domain.Page<Product> getAllProducts(
|
public org.springframework.data.domain.Page<Product> getAllProducts(
|
||||||
|
@RequestParam(required = false) String search,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@RequestParam(defaultValue = "10") int size) {
|
@RequestParam(defaultValue = "10") int size) {
|
||||||
|
if (search != null && !search.isEmpty()) {
|
||||||
|
return productRepository.findByNameOrCategoryContainingIgnoreCase(search, org.springframework.data.domain.PageRequest.of(page, size));
|
||||||
|
}
|
||||||
return productRepository.findAllWithStalls(org.springframework.data.domain.PageRequest.of(page, size));
|
return productRepository.findAllWithStalls(org.springframework.data.domain.PageRequest.of(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,9 +108,10 @@ public class UserController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/users")
|
@GetMapping("/users")
|
||||||
public ResponseEntity<Page<LoginResponse.UserDto>> getAllUsers(
|
public ResponseEntity<Page<LoginResponse.UserDto>> getAllUsers(
|
||||||
|
@RequestParam(required = false) String search,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@RequestParam(defaultValue = "10") int size) {
|
@RequestParam(defaultValue = "10") int size) {
|
||||||
Page<LoginResponse.UserDto> users = userService.getAllUsers(PageRequest.of(page, size));
|
Page<LoginResponse.UserDto> users = userService.getAllUsers(search, PageRequest.of(page, size));
|
||||||
return ResponseEntity.ok(users);
|
return ResponseEntity.ok(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.rit.canteen.sales.counter.config;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterCategory;
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterProduct;
|
||||||
|
import com.rit.canteen.sales.counter.repository.CounterCategoryRepository;
|
||||||
|
import com.rit.canteen.sales.counter.repository.CounterProductRepository;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CounterDataLoader {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CommandLineRunner initCounterDatabase(CounterCategoryRepository categoryRepository, CounterProductRepository productRepository) {
|
||||||
|
return args -> {
|
||||||
|
if (categoryRepository.count() == 0) {
|
||||||
|
CounterCategory iceCream = categoryRepository.save(new CounterCategory("ICE CREAM"));
|
||||||
|
CounterCategory biscuit = categoryRepository.save(new CounterCategory("BISCUIT"));
|
||||||
|
CounterCategory juice = categoryRepository.save(new CounterCategory("READYMADE JUICE"));
|
||||||
|
CounterCategory chocolate = categoryRepository.save(new CounterCategory("CHOCOLATE"));
|
||||||
|
CounterCategory snacks = categoryRepository.save(new CounterCategory("SNACKS"));
|
||||||
|
CounterCategory bakery = categoryRepository.save(new CounterCategory("BAKERY"));
|
||||||
|
|
||||||
|
List<CounterProduct> products = Arrays.asList(
|
||||||
|
new CounterProduct("CM BUTTER SCOTCH CONE", 40.0, 47, iceCream),
|
||||||
|
new CounterProduct("CM CHOCOLATE CONE", 50.0, 24, iceCream),
|
||||||
|
new CounterProduct("CM COOKIES AND CREAM", 50.0, 58, iceCream),
|
||||||
|
new CounterProduct("CM SIPUP CHOCOLATE", 10.0, 111, iceCream),
|
||||||
|
new CounterProduct("CM SIPUP STRAWBERRY", 10.0, 118, iceCream),
|
||||||
|
new CounterProduct("CM SIPUP PINEAPPLE", 10.0, 120, iceCream),
|
||||||
|
new CounterProduct("CM VANILLA CUP", 10.0, 31, iceCream),
|
||||||
|
new CounterProduct("CM PINE APPLE STICK ICE", 10.0, 25, iceCream),
|
||||||
|
new CounterProduct("CM MINI VANILLA CONE", 20.0, 128, iceCream),
|
||||||
|
new CounterProduct("CM PISTA STICK ICE", 20.0, 25, iceCream),
|
||||||
|
new CounterProduct("CM BALL ICE CREAM", 20.0, 18, iceCream),
|
||||||
|
new CounterProduct("CM CHOCOBAR WITH POUCH", 30.0, 100, iceCream),
|
||||||
|
new CounterProduct("CM DILSE BUTTER/CHOCO", 30.0, 18, iceCream),
|
||||||
|
new CounterProduct("CM DILSE REDVEL/VANILLA", 40.0, 20, iceCream),
|
||||||
|
new CounterProduct("CM SIPUP PISTA", 10.0, 118, iceCream),
|
||||||
|
new CounterProduct("COCONUT ICE CREAM", 45.0, 30, iceCream),
|
||||||
|
new CounterProduct("Coke", 25.0, 100, juice)
|
||||||
|
);
|
||||||
|
productRepository.saveAll(products);
|
||||||
|
|
||||||
|
System.out.println("Counter Database seeded with initial categories and products.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.rit.canteen.sales.counter.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CounterWebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
Path uploadDir = Paths.get("uploads").toAbsolutePath().normalize();
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
try {
|
||||||
|
if (!java.nio.file.Files.exists(uploadDir)) {
|
||||||
|
java.nio.file.Files.createDirectories(uploadDir);
|
||||||
|
}
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
System.err.println("Could not create uploads directory: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
String uploadPath = uploadDir.toUri().toString();
|
||||||
|
|
||||||
|
registry.addResourceHandler("/uploads/**")
|
||||||
|
.addResourceLocations(uploadPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.rit.canteen.sales.counter.controller;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.model.BaseItem;
|
||||||
|
import com.rit.canteen.sales.repository.BaseItemRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/counter/categories")
|
||||||
|
public class CounterCategoryController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BaseItemRepository baseItemRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<BaseItem> getAllCategories() {
|
||||||
|
return baseItemRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Management endpoints are disabled for the counter billing dashboard
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.rit.canteen.sales.counter.controller;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
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.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/counter/upload")
|
||||||
|
public class CounterImageUploadController {
|
||||||
|
|
||||||
|
private static final String UPLOAD_DIR = "uploads/products";
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> uploadImage(@RequestParam("file") MultipartFile file) {
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "No file selected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create uploads directory if it doesn't exist
|
||||||
|
Path uploadPath = Paths.get(UPLOAD_DIR).toAbsolutePath().normalize();
|
||||||
|
Files.createDirectories(uploadPath);
|
||||||
|
|
||||||
|
// Generate unique filename to prevent collisions
|
||||||
|
String originalFilename = file.getOriginalFilename();
|
||||||
|
String extension = "";
|
||||||
|
if (originalFilename != null && originalFilename.contains(".")) {
|
||||||
|
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
String uniqueFilename = UUID.randomUUID().toString() + extension;
|
||||||
|
|
||||||
|
// Save the file
|
||||||
|
Path filePath = uploadPath.resolve(uniqueFilename);
|
||||||
|
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
// Return the URL path that the frontend can use
|
||||||
|
String imageUrl = "/uploads/products/" + uniqueFilename;
|
||||||
|
|
||||||
|
return ResponseEntity.ok(Map.of("imageUrl", imageUrl));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body(Map.of("error", "Failed to upload image: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.rit.canteen.sales.counter.controller;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterInfo;
|
||||||
|
import com.rit.canteen.sales.counter.service.CounterInfoService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/counter/counters")
|
||||||
|
public class CounterInfoController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterInfoService counterService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<CounterInfo> getAllCounters() {
|
||||||
|
return counterService.getAllCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public CounterInfo getCounterById(@PathVariable Long id) {
|
||||||
|
return counterService.getCounterById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Counter not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public CounterInfo createCounter(@RequestBody CounterInfo counter) {
|
||||||
|
return counterService.createCounter(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public CounterInfo updateCounter(@PathVariable Long id, @RequestBody CounterInfo counter) {
|
||||||
|
return counterService.updateCounter(id, counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public void deleteCounter(@PathVariable Long id) {
|
||||||
|
counterService.deleteCounter(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{counterId}/assign-product/{productId}")
|
||||||
|
public void assignProduct(@PathVariable Long counterId, @PathVariable Long productId) {
|
||||||
|
counterService.assignProductToCounter(counterId, productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.rit.canteen.sales.counter.controller;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.model.Order;
|
||||||
|
import com.rit.canteen.sales.counter.service.CounterOrderService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/counter/orders")
|
||||||
|
public class CounterOrderController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterOrderService orderService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Order> getAllOrders() {
|
||||||
|
return orderService.getAllOrders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
|
||||||
|
return orderService.getOrderById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Order createOrder(@RequestBody Order order) {
|
||||||
|
return orderService.createOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<Order> updateOrder(@PathVariable Long id, @RequestBody Order orderDetails) {
|
||||||
|
try {
|
||||||
|
Order updatedOrder = orderService.updateOrder(id, orderDetails);
|
||||||
|
return ResponseEntity.ok(updatedOrder);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
|
||||||
|
orderService.deleteOrder(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.rit.canteen.sales.counter.controller;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.model.Product;
|
||||||
|
import com.rit.canteen.sales.repository.ProductRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/counter/products")
|
||||||
|
public class CounterProductController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductRepository productRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Product> getAllProducts() {
|
||||||
|
return productRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/category/{categoryName}")
|
||||||
|
public List<Product> getProductsByCategory(@PathVariable String categoryName) {
|
||||||
|
return productRepository.findByCategory(categoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Management endpoints are disabled for the counter billing dashboard
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.rit.canteen.sales.counter.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "counter_categories")
|
||||||
|
public class CounterCategory {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
|
||||||
|
@com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
private List<CounterProduct> products;
|
||||||
|
|
||||||
|
public CounterCategory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterCategory(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CounterProduct> getProducts() {
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProducts(List<CounterProduct> products) {
|
||||||
|
this.products = products;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.rit.canteen.sales.counter.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "counter_info")
|
||||||
|
public class CounterInfo {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "counter")
|
||||||
|
@com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
private List<CounterProduct> products;
|
||||||
|
|
||||||
|
public CounterInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterInfo(String name, String location) {
|
||||||
|
this.name = name;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocation(String location) {
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CounterProduct> getProducts() {
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProducts(List<CounterProduct> products) {
|
||||||
|
this.products = products;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.rit.canteen.sales.counter.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "counter_orders")
|
||||||
|
public class CounterOrder {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
@JoinColumn(name = "order_id")
|
||||||
|
private List<CounterOrderItem> items;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Double totalAmount;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
public CounterOrder() {
|
||||||
|
this.createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CounterOrderItem> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<CounterOrderItem> items) {
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getTotalAmount() {
|
||||||
|
return totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalAmount(Double totalAmount) {
|
||||||
|
this.totalAmount = totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.rit.canteen.sales.counter.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "counter_order_items")
|
||||||
|
public class CounterOrderItem {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "product_name", nullable = false)
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
@Column(name = "unit_price", nullable = false)
|
||||||
|
private Double unitPrice;
|
||||||
|
|
||||||
|
@Column(name = "total_price", nullable = false)
|
||||||
|
private Double totalPrice;
|
||||||
|
|
||||||
|
@Column(name = "product_id")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
public CounterOrderItem() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(Long productId) {
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductName(String productName) {
|
||||||
|
this.productName = productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Integer quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getUnitPrice() {
|
||||||
|
return unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitPrice(Double unitPrice) {
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getTotalPrice() {
|
||||||
|
return totalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalPrice(Double totalPrice) {
|
||||||
|
this.totalPrice = totalPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.rit.canteen.sales.counter.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "counter_products")
|
||||||
|
public class CounterProduct {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer stock;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "category_id")
|
||||||
|
private CounterCategory category;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "counter_id")
|
||||||
|
private CounterInfo counter;
|
||||||
|
|
||||||
|
public CounterProduct() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterProduct(String name, Double price, Integer stock, CounterCategory category) {
|
||||||
|
this.name = name;
|
||||||
|
this.price = price;
|
||||||
|
this.stock = stock;
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStock() {
|
||||||
|
return stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStock(Integer stock) {
|
||||||
|
this.stock = stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageUrl() {
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUrl(String imageUrl) {
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterCategory getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(CounterCategory category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterInfo getCounter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCounter(CounterInfo counter) {
|
||||||
|
this.counter = counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.rit.canteen.sales.counter.repository;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterCategory;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CounterCategoryRepository extends JpaRepository<CounterCategory, Long> {
|
||||||
|
Optional<CounterCategory> findByName(String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.rit.canteen.sales.counter.repository;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterInfo;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CounterInfoRepository extends JpaRepository<CounterInfo, Long> {
|
||||||
|
Optional<CounterInfo> findByName(String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.rit.canteen.sales.counter.repository;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterOrder;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CounterOrderRepository extends JpaRepository<CounterOrder, Long> {
|
||||||
|
List<CounterOrder> findByStatus(String status);
|
||||||
|
List<CounterOrder> findAllByOrderByCreatedAtDesc();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.rit.canteen.sales.counter.repository;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterCategory;
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterProduct;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CounterProductRepository extends JpaRepository<CounterProduct, Long> {
|
||||||
|
List<CounterProduct> findByCategoryName(String categoryName);
|
||||||
|
List<CounterProduct> findByCategory(CounterCategory category);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.rit.canteen.sales.counter.service;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterCategory;
|
||||||
|
import com.rit.canteen.sales.counter.repository.CounterCategoryRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CounterCategoryService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterCategoryRepository categoryRepository;
|
||||||
|
|
||||||
|
public List<CounterCategory> getAllCategories() {
|
||||||
|
return categoryRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<CounterCategory> getCategoryByName(String name) {
|
||||||
|
return categoryRepository.findByName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterCategory createCategory(CounterCategory category) {
|
||||||
|
return categoryRepository.save(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterCategory updateCategory(Long id, CounterCategory categoryDetails) {
|
||||||
|
CounterCategory category = categoryRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Category not found"));
|
||||||
|
category.setName(categoryDetails.getName());
|
||||||
|
return categoryRepository.save(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteCategory(Long id) {
|
||||||
|
categoryRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.rit.canteen.sales.counter.service;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterInfo;
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterProduct;
|
||||||
|
import com.rit.canteen.sales.counter.repository.CounterInfoRepository;
|
||||||
|
import com.rit.canteen.sales.counter.repository.CounterProductRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CounterInfoService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterInfoRepository counterRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterProductRepository productRepository;
|
||||||
|
|
||||||
|
public List<CounterInfo> getAllCounters() {
|
||||||
|
return counterRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<CounterInfo> getCounterById(Long id) {
|
||||||
|
return counterRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterInfo createCounter(CounterInfo counter) {
|
||||||
|
return counterRepository.save(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterInfo updateCounter(Long id, CounterInfo counterDetails) {
|
||||||
|
CounterInfo counter = counterRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Counter not found"));
|
||||||
|
counter.setName(counterDetails.getName());
|
||||||
|
counter.setLocation(counterDetails.getLocation());
|
||||||
|
return counterRepository.save(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteCounter(Long id) {
|
||||||
|
counterRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assignProductToCounter(Long counterId, Long productId) {
|
||||||
|
CounterInfo counter = counterRepository.findById(counterId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Counter not found"));
|
||||||
|
CounterProduct product = productRepository.findById(productId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Product not found"));
|
||||||
|
|
||||||
|
product.setCounter(counter);
|
||||||
|
productRepository.save(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.rit.canteen.sales.counter.service;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.model.Order;
|
||||||
|
import com.rit.canteen.sales.model.OrderItem;
|
||||||
|
import com.rit.canteen.sales.repository.OrderRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CounterOrderService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderRepository orderRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterProductService productService;
|
||||||
|
|
||||||
|
public List<Order> getAllOrders() {
|
||||||
|
return orderRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Order> getOrderById(Long id) {
|
||||||
|
return orderRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order createOrder(Order order) {
|
||||||
|
order.setCreatedAt(LocalDateTime.now());
|
||||||
|
if (order.getStatus() == null) {
|
||||||
|
order.setStatus("COMPLETED");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure bidirectional relationship for items
|
||||||
|
if (order.getItems() != null) {
|
||||||
|
for (OrderItem item : order.getItems()) {
|
||||||
|
item.setOrder(order);
|
||||||
|
// Update stock if product ID is present
|
||||||
|
if (item.getProductId() != null) {
|
||||||
|
productService.updateStock(item.getProductId(), item.getQuantity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderRepository.save(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order updateOrder(Long id, Order orderDetails) {
|
||||||
|
Order order = orderRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Order not found"));
|
||||||
|
|
||||||
|
order.setItems(orderDetails.getItems());
|
||||||
|
order.setTotalAmount(orderDetails.getTotalAmount());
|
||||||
|
order.setStatus(orderDetails.getStatus());
|
||||||
|
|
||||||
|
return orderRepository.save(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteOrder(Long id) {
|
||||||
|
orderRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.rit.canteen.sales.counter.service;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.counter.model.CounterProduct;
|
||||||
|
import com.rit.canteen.sales.counter.repository.CounterProductRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CounterProductService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CounterProductRepository productRepository;
|
||||||
|
|
||||||
|
public List<CounterProduct> getAllProducts() {
|
||||||
|
return productRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CounterProduct> getProductsByCategory(String categoryName) {
|
||||||
|
return productRepository.findByCategoryName(categoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<CounterProduct> getProductById(Long id) {
|
||||||
|
return productRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterProduct createProduct(CounterProduct product) {
|
||||||
|
return productRepository.save(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterProduct updateProduct(Long id, CounterProduct productDetails) {
|
||||||
|
CounterProduct product = productRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Product not found"));
|
||||||
|
|
||||||
|
product.setName(productDetails.getName());
|
||||||
|
product.setPrice(productDetails.getPrice());
|
||||||
|
product.setStock(productDetails.getStock());
|
||||||
|
product.setDescription(productDetails.getDescription());
|
||||||
|
product.setImageUrl(productDetails.getImageUrl());
|
||||||
|
product.setCategory(productDetails.getCategory());
|
||||||
|
product.setCounter(productDetails.getCounter());
|
||||||
|
|
||||||
|
return productRepository.save(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteProduct(Long id) {
|
||||||
|
productRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterProduct updateStock(Long productId, Integer quantity) {
|
||||||
|
CounterProduct product = productRepository.findById(productId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Product not found"));
|
||||||
|
product.setStock(product.getStock() - quantity);
|
||||||
|
return productRepository.save(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
package com.rit.canteen.sales.model;
|
package com.rit.canteen.sales.model;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -18,9 +14,11 @@ public class BaseItem {
|
|||||||
@NotBlank(message = "Item name is required")
|
@NotBlank(message = "Item name is required")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
private boolean active;
|
@Column(nullable = false)
|
||||||
|
private boolean active = true;
|
||||||
|
|
||||||
public BaseItem() {}
|
public BaseItem() {}
|
||||||
|
|
||||||
@@ -30,35 +28,15 @@ public class BaseItem {
|
|||||||
this.active = active;
|
this.active = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() { return id; }
|
||||||
return id;
|
public void setId(Long id) { this.id = id; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
public String getName() { return name; }
|
||||||
this.id = id;
|
public void setName(String name) { this.name = name; }
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
public String getDescription() { return description; }
|
||||||
return name;
|
public void setDescription(String description) { this.description = description; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
public boolean isActive() { return active; }
|
||||||
this.name = name;
|
public void setActive(boolean active) { this.active = active; }
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive() {
|
|
||||||
return active;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActive(boolean active) {
|
|
||||||
this.active = active;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,17 @@ public class DashboardStats {
|
|||||||
private long totalSales;
|
private long totalSales;
|
||||||
private int activeOrders;
|
private int activeOrders;
|
||||||
private int dailyCustomers;
|
private int dailyCustomers;
|
||||||
private double revenueGrowth;
|
private double growth;
|
||||||
|
|
||||||
public DashboardStats(long totalSales, int activeOrders, int dailyCustomers, double revenueGrowth) {
|
public DashboardStats() {}
|
||||||
|
|
||||||
|
public DashboardStats(long totalSales, int activeOrders, int dailyCustomers, double growth) {
|
||||||
this.totalSales = totalSales;
|
this.totalSales = totalSales;
|
||||||
this.activeOrders = activeOrders;
|
this.activeOrders = activeOrders;
|
||||||
this.dailyCustomers = dailyCustomers;
|
this.dailyCustomers = dailyCustomers;
|
||||||
this.revenueGrowth = revenueGrowth;
|
this.growth = growth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters and Setters
|
|
||||||
public long getTotalSales() { return totalSales; }
|
public long getTotalSales() { return totalSales; }
|
||||||
public void setTotalSales(long totalSales) { this.totalSales = totalSales; }
|
public void setTotalSales(long totalSales) { this.totalSales = totalSales; }
|
||||||
|
|
||||||
@@ -23,6 +24,6 @@ public class DashboardStats {
|
|||||||
public int getDailyCustomers() { return dailyCustomers; }
|
public int getDailyCustomers() { return dailyCustomers; }
|
||||||
public void setDailyCustomers(int dailyCustomers) { this.dailyCustomers = dailyCustomers; }
|
public void setDailyCustomers(int dailyCustomers) { this.dailyCustomers = dailyCustomers; }
|
||||||
|
|
||||||
public double getRevenueGrowth() { return revenueGrowth; }
|
public double getGrowth() { return growth; }
|
||||||
public void setRevenueGrowth(double revenueGrowth) { this.revenueGrowth = revenueGrowth; }
|
public void setGrowth(double growth) { this.growth = growth; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class GeneralDashboardData {
|
|||||||
private List<Map<String, Object>> hourlySales;
|
private List<Map<String, Object>> hourlySales;
|
||||||
private List<String> insights;
|
private List<String> insights;
|
||||||
|
|
||||||
|
public GeneralDashboardData() {}
|
||||||
|
|
||||||
public GeneralDashboardData(DashboardStats stats, List<Map<String, Object>> storeOverview, List<Map<String, Object>> hourlySales, List<String> insights) {
|
public GeneralDashboardData(DashboardStats stats, List<Map<String, Object>> storeOverview, List<Map<String, Object>> hourlySales, List<String> insights) {
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
this.storeOverview = storeOverview;
|
this.storeOverview = storeOverview;
|
||||||
@@ -16,9 +18,15 @@ public class GeneralDashboardData {
|
|||||||
this.insights = insights;
|
this.insights = insights;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
|
||||||
public DashboardStats getStats() { return stats; }
|
public DashboardStats getStats() { return stats; }
|
||||||
|
public void setStats(DashboardStats stats) { this.stats = stats; }
|
||||||
|
|
||||||
public List<Map<String, Object>> getStoreOverview() { return storeOverview; }
|
public List<Map<String, Object>> getStoreOverview() { return storeOverview; }
|
||||||
|
public void setStoreOverview(List<Map<String, Object>> storeOverview) { this.storeOverview = storeOverview; }
|
||||||
|
|
||||||
public List<Map<String, Object>> getHourlySales() { return hourlySales; }
|
public List<Map<String, Object>> getHourlySales() { return hourlySales; }
|
||||||
|
public void setHourlySales(List<Map<String, Object>> hourlySales) { this.hourlySales = hourlySales; }
|
||||||
|
|
||||||
public List<String> getInsights() { return insights; }
|
public List<String> getInsights() { return insights; }
|
||||||
|
public void setInsights(List<String> insights) { this.insights = insights; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ public class Order {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String orderType = "STORE_ORDER"; // STORE_ORDER, QR_ORDER, MY_ORDER, etc.
|
||||||
|
|
||||||
@Column(name = "is_archived", nullable = false)
|
@Column(name = "is_archived", nullable = false)
|
||||||
private boolean isArchived = false;
|
private boolean isArchived = false;
|
||||||
|
|
||||||
@@ -97,6 +100,9 @@ public class Order {
|
|||||||
public boolean isArchived() { return isArchived; }
|
public boolean isArchived() { return isArchived; }
|
||||||
public void setArchived(boolean archived) { isArchived = archived; }
|
public void setArchived(boolean archived) { isArchived = archived; }
|
||||||
|
|
||||||
|
public String getOrderType() { return orderType; }
|
||||||
|
public void setOrderType(String orderType) { this.orderType = orderType; }
|
||||||
|
|
||||||
public List<OrderItem> getItems() { return items; }
|
public List<OrderItem> getItems() { return items; }
|
||||||
public void setItems(List<OrderItem> items) { this.items = items; }
|
public void setItems(List<OrderItem> items) { this.items = items; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.rit.canteen.sales.repository;
|
package com.rit.canteen.sales.repository;
|
||||||
|
|
||||||
import com.rit.canteen.sales.model.BaseItem;
|
import com.rit.canteen.sales.model.BaseItem;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface BaseItemRepository extends JpaRepository<BaseItem, Long> {
|
public interface BaseItemRepository extends JpaRepository<BaseItem, Long> {
|
||||||
boolean existsByName(String name);
|
boolean existsByName(String name);
|
||||||
|
Page<BaseItem> findByNameContainingIgnoreCase(String name, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import java.util.List;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ProductRepository extends JpaRepository<Product, Long> {
|
public interface ProductRepository extends JpaRepository<Product, Long> {
|
||||||
|
@Query("SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls " +
|
||||||
|
"WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', :search, '%')) " +
|
||||||
|
"OR LOWER(p.category) LIKE LOWER(CONCAT('%', :search, '%'))")
|
||||||
|
org.springframework.data.domain.Page<Product> findByNameOrCategoryContainingIgnoreCase(String search, org.springframework.data.domain.Pageable pageable);
|
||||||
|
|
||||||
@Query(value = "SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls",
|
@Query(value = "SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls",
|
||||||
countQuery = "SELECT count(p) FROM Product p")
|
countQuery = "SELECT count(p) FROM Product p")
|
||||||
org.springframework.data.domain.Page<Product> findAllWithStalls(org.springframework.data.domain.Pageable pageable);
|
org.springframework.data.domain.Page<Product> findAllWithStalls(org.springframework.data.domain.Pageable pageable);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.rit.canteen.sales.repository;
|
|||||||
|
|
||||||
import com.rit.canteen.sales.model.User;
|
import com.rit.canteen.sales.model.User;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -10,4 +11,8 @@ import java.util.Optional;
|
|||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
Optional<User> findByMobileNumber(String mobileNumber);
|
Optional<User> findByMobileNumber(String mobileNumber);
|
||||||
boolean existsByMobileNumber(String mobileNumber);
|
boolean existsByMobileNumber(String mobileNumber);
|
||||||
|
|
||||||
|
@Query("SELECT u FROM User u WHERE LOWER(u.name) LIKE LOWER(CONCAT('%', :search, '%')) " +
|
||||||
|
"OR u.mobileNumber LIKE CONCAT('%', :search, '%')")
|
||||||
|
org.springframework.data.domain.Page<User> findByNameOrMobileContainingIgnoreCase(String search, org.springframework.data.domain.Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class DashboardService {
|
|||||||
hourMap.put("value", 0);
|
hourMap.put("value", 0);
|
||||||
|
|
||||||
for (Object[] row : hourlyData) {
|
for (Object[] row : hourlyData) {
|
||||||
int h = (int) row[0];
|
int h = ((Number) row[0]).intValue();
|
||||||
if (h >= i && h < i + 2) {
|
if (h >= i && h < i + 2) {
|
||||||
BigDecimal val = (BigDecimal) row[1];
|
BigDecimal val = (BigDecimal) row[1];
|
||||||
hourMap.put("value", ((Number) hourMap.get("value")).doubleValue() + val.doubleValue());
|
hourMap.put("value", ((Number) hourMap.get("value")).doubleValue() + val.doubleValue());
|
||||||
|
|||||||
@@ -158,11 +158,17 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all registered users for administration dashboard (Paginated).
|
* Get all registered users for administration dashboard (Paginated with Search).
|
||||||
*/
|
*/
|
||||||
public Page<LoginResponse.UserDto> getAllUsers(Pageable pageable) {
|
public Page<LoginResponse.UserDto> getAllUsers(String search, Pageable pageable) {
|
||||||
return userRepository.findAll(pageable)
|
Page<User> users;
|
||||||
.map(user -> new LoginResponse.UserDto(
|
if (search != null && !search.isEmpty()) {
|
||||||
|
users = userRepository.findByNameOrMobileContainingIgnoreCase(search, pageable);
|
||||||
|
} else {
|
||||||
|
users = userRepository.findAll(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return users.map(user -> new LoginResponse.UserDto(
|
||||||
user.getId(),
|
user.getId(),
|
||||||
user.getMobileNumber(),
|
user.getMobileNumber(),
|
||||||
user.getName(),
|
user.getName(),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const BaseMenu = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [totalElements, setTotalElements] = useState(0);
|
const [totalElements, setTotalElements] = useState(0);
|
||||||
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
||||||
|
|
||||||
// Associated Products State
|
// Associated Products State
|
||||||
const [selectedBaseItem, setSelectedBaseItem] = useState<BaseItem | null>(null);
|
const [selectedBaseItem, setSelectedBaseItem] = useState<BaseItem | null>(null);
|
||||||
@@ -39,9 +40,24 @@ const BaseMenu = () => {
|
|||||||
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Debounce search term
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedSearchTerm(searchTerm);
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
// Reset to first page when search changes
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(0);
|
||||||
|
}, [debouncedSearchTerm]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchItems();
|
fetchItems();
|
||||||
|
}, [currentPage, pageSize, debouncedSearchTerm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||||
setOpenMenuId(null);
|
setOpenMenuId(null);
|
||||||
@@ -49,13 +65,20 @@ const BaseMenu = () => {
|
|||||||
};
|
};
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
}, [currentPage, pageSize]);
|
}, []);
|
||||||
|
|
||||||
const fetchItems = async () => {
|
const fetchItems = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
const response = await fetch(`http://${host}:8080/api/base-items?page=${currentPage}&size=${pageSize}`);
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', currentPage.toString());
|
||||||
|
params.append('size', pageSize.toString());
|
||||||
|
if (debouncedSearchTerm) {
|
||||||
|
params.append('search', debouncedSearchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`http://${host}:8080/api/base-items?${params.toString()}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data && data.content) {
|
if (data && data.content) {
|
||||||
setItems(data.content);
|
setItems(data.content);
|
||||||
@@ -200,17 +223,15 @@ const BaseMenu = () => {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? (
|
) : items.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">
|
<td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">
|
||||||
<p className="text-lg font-medium mb-1">No items found</p>
|
<p className="text-lg font-medium mb-1">No items found</p>
|
||||||
<p className="text-sm">Click "Add New Item" to create your first item.</p>
|
<p className="text-sm">Try adjusting your search or add a new item.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
items
|
items.map((item) => (
|
||||||
.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
||||||
.map((item) => (
|
|
||||||
<tr key={item.id} className="hover:bg-gray-50/50 transition-all group">
|
<tr key={item.id} className="hover:bg-gray-50/50 transition-all group">
|
||||||
<td className="px-6 py-4 text-sm font-medium text-[#64748b]">#{item.id}</td>
|
<td className="px-6 py-4 text-sm font-medium text-[#64748b]">#{item.id}</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const Customers: React.FC = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [totalElements, setTotalElements] = useState(0);
|
const [totalElements, setTotalElements] = useState(0);
|
||||||
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
||||||
|
|
||||||
// Edit Modal State
|
// Edit Modal State
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
@@ -34,15 +35,33 @@ const Customers: React.FC = () => {
|
|||||||
// Action Menu State
|
// Action Menu State
|
||||||
const [openMenuId, setOpenMenuId] = useState<number | null>(null);
|
const [openMenuId, setOpenMenuId] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedSearchTerm(searchTerm);
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(0);
|
||||||
|
}, [debouncedSearchTerm]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
}, [currentPage, pageSize]);
|
}, [currentPage, pageSize, debouncedSearchTerm]);
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
const response = await fetch(`http://${host}:8080/api/auth/users?page=${currentPage}&size=${pageSize}`);
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', currentPage.toString());
|
||||||
|
params.append('size', pageSize.toString());
|
||||||
|
if (debouncedSearchTerm) {
|
||||||
|
params.append('search', debouncedSearchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`http://${host}:8080/api/auth/users?${params.toString()}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data && data.content) {
|
if (data && data.content) {
|
||||||
@@ -146,11 +165,6 @@ const Customers: React.FC = () => {
|
|||||||
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredUsers = users.filter(user =>
|
|
||||||
user.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
user.mobileNumber?.includes(searchTerm)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Toast Notification - Moved outside animation trapping */}
|
{/* Toast Notification - Moved outside animation trapping */}
|
||||||
@@ -232,8 +246,8 @@ const Customers: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-[#e2e8f0]">
|
<tbody className="divide-y divide-[#e2e8f0]">
|
||||||
{filteredUsers.length > 0 ? (
|
{users.length > 0 ? (
|
||||||
filteredUsers.map((user) => (
|
users.map((user) => (
|
||||||
<tr key={user.id} className="hover:bg-gray-50/50 transition-all">
|
<tr key={user.id} className="hover:bg-gray-50/50 transition-all">
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -300,7 +314,7 @@ const Customers: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-6">
|
||||||
{filteredUsers.map(user => (
|
{users.map(user => (
|
||||||
<div key={user.id} className="group relative p-6 bg-white border border-[#e2e8f0] rounded-xl flex flex-col items-center text-center transition-all hover:shadow-lg hover:-translate-y-1">
|
<div key={user.id} className="group relative p-6 bg-white border border-[#e2e8f0] rounded-xl flex flex-col items-center text-center transition-all hover:shadow-lg hover:-translate-y-1">
|
||||||
<div className="absolute top-4 right-4">
|
<div className="absolute top-4 right-4">
|
||||||
<button onClick={() => setOpenMenuId(openMenuId === user.id ? null : user.id)} className="p-1.5 text-[#94a3b8] hover:text-[#231651] rounded-lg">
|
<button onClick={() => setOpenMenuId(openMenuId === user.id ? null : user.id)} className="p-1.5 text-[#94a3b8] hover:text-[#231651] rounded-lg">
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const Orders: React.FC = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [totalElements, setTotalElements] = useState(0);
|
const [totalElements, setTotalElements] = useState(0);
|
||||||
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
|
||||||
|
|
||||||
// Action Menu & Edit Modal States
|
// Action Menu & Edit Modal States
|
||||||
const [showActionMenu, setShowActionMenu] = useState(false);
|
const [showActionMenu, setShowActionMenu] = useState(false);
|
||||||
@@ -75,10 +76,26 @@ const Orders: React.FC = () => {
|
|||||||
const [editingItems, setEditingItems] = useState<OrderItem[]>([]);
|
const [editingItems, setEditingItems] = useState<OrderItem[]>([]);
|
||||||
const [isUpdatingOrder, setIsUpdatingOrder] = useState(false);
|
const [isUpdatingOrder, setIsUpdatingOrder] = useState(false);
|
||||||
|
|
||||||
|
// Debounce search query
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedSearchQuery(searchQuery);
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
// Reset to first page when search changes
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(0);
|
||||||
|
}, [debouncedSearchQuery]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchOrders();
|
fetchOrders();
|
||||||
|
}, [startDate, endDate, statusFilter, paymentFilter, currentPage, pageSize, debouncedSearchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
}, [startDate, endDate, statusFilter, paymentFilter, currentPage, pageSize]);
|
}, []);
|
||||||
|
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -102,7 +119,7 @@ const Orders: React.FC = () => {
|
|||||||
if (endDate) params.append('endDate', `${endDate}T23:59:59`);
|
if (endDate) params.append('endDate', `${endDate}T23:59:59`);
|
||||||
if (statusFilter) params.append('status', statusFilter);
|
if (statusFilter) params.append('status', statusFilter);
|
||||||
if (paymentFilter) params.append('paymentType', paymentFilter);
|
if (paymentFilter) params.append('paymentType', paymentFilter);
|
||||||
if (searchQuery) params.append('search', searchQuery);
|
if (debouncedSearchQuery) params.append('search', debouncedSearchQuery);
|
||||||
params.append('page', currentPage.toString());
|
params.append('page', currentPage.toString());
|
||||||
params.append('size', pageSize.toString());
|
params.append('size', pageSize.toString());
|
||||||
|
|
||||||
|
|||||||
@@ -94,14 +94,32 @@ const Products = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [totalElements, setTotalElements] = useState(0);
|
const [totalElements, setTotalElements] = useState(0);
|
||||||
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
||||||
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
// Debounce search term
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedSearchTerm(searchTerm);
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
// Reset to first page when search changes
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(0);
|
||||||
|
}, [debouncedSearchTerm]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
|
}, [currentPage, pageSize, debouncedSearchTerm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchBaseItems();
|
fetchBaseItems();
|
||||||
fetchAllStalls();
|
fetchAllStalls();
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||||
setOpenMenuId(null);
|
setOpenMenuId(null);
|
||||||
@@ -109,13 +127,20 @@ const Products = () => {
|
|||||||
};
|
};
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
}, [currentPage, pageSize]);
|
}, []);
|
||||||
|
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
const response = await fetch(`http://${host}:8080/api/products?page=${currentPage}&size=${pageSize}`);
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', currentPage.toString());
|
||||||
|
params.append('size', pageSize.toString());
|
||||||
|
if (debouncedSearchTerm) {
|
||||||
|
params.append('search', debouncedSearchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`http://${host}:8080/api/products?${params.toString()}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data && data.content) {
|
if (data && data.content) {
|
||||||
setProducts(data.content);
|
setProducts(data.content);
|
||||||
@@ -133,9 +158,9 @@ const Products = () => {
|
|||||||
|
|
||||||
const fetchBaseItems = async () => {
|
const fetchBaseItems = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:8080/api/base-items');
|
const response = await fetch('http://localhost:8080/api/base-items?size=100');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setBaseItems(data);
|
setBaseItems(data.content || data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching base items:', error);
|
console.error('Error fetching base items:', error);
|
||||||
}
|
}
|
||||||
@@ -303,9 +328,9 @@ const Products = () => {
|
|||||||
<tbody className="divide-y divide-[#e2e8f0]">
|
<tbody className="divide-y divide-[#e2e8f0]">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]"><RefreshCw className="animate-spin inline mr-2" />Loading...</td></tr>
|
<tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]"><RefreshCw className="animate-spin inline mr-2" />Loading...</td></tr>
|
||||||
) : products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.category.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? (
|
) : products.length === 0 ? (
|
||||||
<tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">No products found</td></tr>
|
<tr><td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">No products found</td></tr>
|
||||||
) : products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.category.toLowerCase().includes(searchTerm.toLowerCase())).map((product) => (
|
) : products.map((product) => (
|
||||||
<tr key={product.id} className="hover:bg-gray-50/50 transition-all">
|
<tr key={product.id} className="hover:bg-gray-50/50 transition-all">
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -194,12 +194,18 @@ const Stalls: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
const [prodRes, baseRes] = await Promise.all([
|
const [prodRes, baseRes] = await Promise.all([
|
||||||
fetch(`http://${host}:8080/api/products`),
|
fetch(`http://${host}:8080/api/products?size=1000`),
|
||||||
fetch(`http://${host}:8080/api/base-items`)
|
fetch(`http://${host}:8080/api/base-items?size=100`)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (prodRes.ok) setAllProducts(await prodRes.json());
|
if (prodRes.ok) {
|
||||||
if (baseRes.ok) setAllBaseItems(await baseRes.json());
|
const data = await prodRes.json();
|
||||||
|
setAllProducts(data.content || data);
|
||||||
|
}
|
||||||
|
if (baseRes.ok) {
|
||||||
|
const data = await baseRes.json();
|
||||||
|
setAllBaseItems(data.content || data);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching available items:', error);
|
console.error('Error fetching available items:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user