diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/BaseItemController.java b/backend/src/main/java/com/rit/canteen/sales/controller/BaseItemController.java index 77f00add..19f6498a 100644 --- a/backend/src/main/java/com/rit/canteen/sales/controller/BaseItemController.java +++ b/backend/src/main/java/com/rit/canteen/sales/controller/BaseItemController.java @@ -17,8 +17,10 @@ public class BaseItemController { private BaseItemRepository baseItemRepository; @GetMapping - public List getAllBaseItems() { - return baseItemRepository.findAll(); + public org.springframework.data.domain.Page getAllBaseItems( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return baseItemRepository.findAll(org.springframework.data.domain.PageRequest.of(page, size)); } @PostMapping diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java b/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java index 0c823b6f..89c8aa7a 100644 --- a/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java +++ b/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java @@ -17,8 +17,10 @@ public class ProductController { private ProductRepository productRepository; @GetMapping - public List getAllProducts() { - return productRepository.findAllWithStalls(); + public org.springframework.data.domain.Page getAllProducts( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return productRepository.findAllWithStalls(org.springframework.data.domain.PageRequest.of(page, size)); } @GetMapping("/category/{categoryName}") diff --git a/backend/src/main/java/com/rit/canteen/sales/repository/BaseItemRepository.java b/backend/src/main/java/com/rit/canteen/sales/repository/BaseItemRepository.java index 9f354ba0..c4244749 100644 --- a/backend/src/main/java/com/rit/canteen/sales/repository/BaseItemRepository.java +++ b/backend/src/main/java/com/rit/canteen/sales/repository/BaseItemRepository.java @@ -6,4 +6,5 @@ import org.springframework.stereotype.Repository; @Repository public interface BaseItemRepository extends JpaRepository { + boolean existsByName(String name); } diff --git a/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java b/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java index 4cb0f002..8be9ed06 100644 --- a/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java +++ b/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java @@ -9,8 +9,11 @@ import java.util.List; @Repository public interface ProductRepository extends JpaRepository { - @Query("SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls") - List findAllWithStalls(); + @Query(value = "SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls", + countQuery = "SELECT count(p) FROM Product p") + org.springframework.data.domain.Page findAllWithStalls(org.springframework.data.domain.Pageable pageable); List findByCategory(String category); + + boolean existsByNameAndCategory(String name, String category); } diff --git a/backend/src/main/java/com/rit/canteen/sales/service/DatabaseSeeder.java b/backend/src/main/java/com/rit/canteen/sales/service/DatabaseSeeder.java index 1c451c9f..949fc659 100644 --- a/backend/src/main/java/com/rit/canteen/sales/service/DatabaseSeeder.java +++ b/backend/src/main/java/com/rit/canteen/sales/service/DatabaseSeeder.java @@ -28,12 +28,8 @@ public class DatabaseSeeder implements CommandLineRunner { public void run(String... args) throws Exception { repairStallsSchema(); repairLobColumns(); - if (baseItemRepository.count() == 0) { - seedCategories(); - } - if (productRepository.count() == 0) { - seedProducts(); - } + seedCategories(); + seedProducts(); } private void repairStallsSchema() { @@ -81,27 +77,115 @@ public class DatabaseSeeder implements CommandLineRunner { } private void seedCategories() { - BaseItem breakfast = new BaseItem("Breakfast", "Morning favorites", true); - BaseItem lunch = new BaseItem("Lunch", "Hearty meals", true); - BaseItem snacks = new BaseItem("Snacks", "Quick bites", true); - BaseItem beverages = new BaseItem("Beverages", "Refreshing drinks", true); - BaseItem desserts = new BaseItem("Desserts", "Sweet treats", true); + String[][] categories = { + {"Fast Food", "Burgers, Pizzas and more"}, + {"Indian Main Course", "Traditional Indian meals"}, + {"Chinese", "Oriental flavors"}, + {"Beverages & Drinks", "Refreshing drinks and coffee"}, + {"Healthy & Salads", "Nutritious and light options"}, + {"Snacks & Quick Bites", "Classic street food and snacks"}, + {"Bakery & Sweets", "Cakes, pastries and desserts"} + }; - baseItemRepository.saveAll(List.of(breakfast, lunch, snacks, beverages, desserts)); - System.out.println("Categories seeded!"); + for (String[] cat : categories) { + if (!baseItemRepository.existsByName(cat[0])) { + baseItemRepository.save(new BaseItem(cat[0], cat[1], true)); + System.out.println("Seeded category: " + cat[0]); + } + } } private void seedProducts() { - Product dosa = createProduct("Masala Dosa", "Breakfast", "Crispy dosa with potato filling", 60.0, 50, true); - Product poha = createProduct("Poha", "Breakfast", "Flattened rice with onions and spices", 30.0, 40, true); - Product thali = createProduct("Veg Thali", "Lunch", "Full meal with roti, sabzi, dal and rice", 120.0, 30, true); - Product biryani = createProduct("Chicken Biryani", "Lunch", "Aromatic rice with spiced chicken", 150.0, 20, false); - Product samosa = createProduct("Samosa", "Snacks", "Deep fried pastry with potato filling", 15.0, 100, true); - Product tea = createProduct("Masala Tea", "Beverages", "Traditional Indian spiced tea", 10.0, 200, true); - Product iceCream = createProduct("Vanilla Ice Cream", "Desserts", "Classic vanilla flavor", 30.0, 0, true); // Out of stock + // Fast Food + addProductsToCategory("Fast Food", new String[]{ + "Veg Burger", "Chicken Burger", "Cheese Burger", "Double Patty Burger", "Spicy Zinger Burger", + "Paneer Burger", "Aloha Burger", "Mushroom Burger", "Fish Burger", "BBQ Burger", + "Margherita Pizza", "Farmhouse Pizza", "Peppy Paneer", "Mexican Wave", "Pepper BBQ Chicken", + "Chicken Tikka Pizza", "Meat Lover's Pizza", "Veggie Paradise", "Corn & Cheese Pizza", "Double Cheese Margherita", + "Classic Fries", "Peri Peri Fries", "Cheese Loaded Fries", "Onion Rings", "Garlic Bread Sticks" + }, 50.0, 350.0); - productRepository.saveAll(List.of(dosa, poha, thali, biryani, samosa, tea, iceCream)); - System.out.println("Products seeded!"); + // Indian Main Course + addProductsToCategory("Indian Main Course", new String[]{ + "Paneer Butter Masala", "Kadai Paneer", "Palak Paneer", "Matar Paneer", "Paneer Tikka Masala", + "Dal Makhani", "Dal Tadka", "Yellow Dal Fry", "Chole Masala", "Rajma Masala", + "Veg Kolhapuri", "Mix Veg", "Malai Kofta", "Bhindi Do Pyaza", "Aloo Gobi", + "Butter Chicken", "Chicken Tikka Masala", "Mutton Rogan Josh", "Egg Curry", "Fish Curry", + "Jeera Rice", "Steamed Rice", "Veg Pulao", "Peas Pulao", "Kashmiri Pulao" + }, 100.0, 450.0); + + // Chinese + addProductsToCategory("Chinese", new String[]{ + "Veg Hakka Noodles", "Schezwan Noodles", "Chilli Garlic Noodles", "Singapore Noodles", "Manchow Noodles", + "Veg Fried Rice", "Egg Fried Rice", "Chicken Fried Rice", "Mixed Fried Rice", "Schezwan Fried Rice", + "Veg Manchurian", "Chicken Manchurian", "Gobi Manchurian", "Baby Corn Manchurian", "Mushroom Manchurian", + "Chilli Paneer", "Chilli Chicken", "Chilli Potato", "Crispy Corn", "Spring Rolls", + "Hot and Sour Soup", "Sweet Corn Soup", "Lemon Coriander Soup", "Clear Soup", "Wonton Soup" + }, 80.0, 280.0); + + // Beverages & Drinks + addProductsToCategory("Beverages & Drinks", new String[]{ + "Fresh Lime Soda", "Virgin Mojito", "Blue Lagoon", "Iced Tea", "Cold Coffee", + "Mango Shake", "Oreo Shake", "Chocolate Shake", "Strawberry Shake", "Vanilla Shake", + "Masala Tea", "Ginger Tea", "Green Tea", "Black Coffee", "Cappuccino", "Latte", + "Orange Juice", "Watermelon Juice", "Pineapple Juice", "Mixed Fruit Juice", "Mosambi Juice", + "Coca Cola", "Pepsi", "Sprite", "Fanta", "Mineral Water" + }, 20.0, 180.0); + + // Healthy & Salads + addProductsToCategory("Healthy & Salads", new String[]{ + "Caesar Salad", "Greek Salad", "Garden Salad", "Fruit Salad", "Russian Salad", + "Sprouted Moong Salad", "Chickpea Salad", "Cucumber Salad", "Tomato Onion Salad", "Beetroot Salad", + "Grilled Chicken Salad", "Tuna Salad", "Boiled Egg Salad", "Paneer Salad", "Tofu Salad", + "Vegetable Clear Soup", "Quinoa Bowl", "Brown Rice Bowl", "Steamed Vegetables", "Sautéed Spinach" + }, 120.0, 320.0); + + // Snacks & Quick Bites + addProductsToCategory("Snacks & Quick Bites", new String[]{ + "Samosa", "Vada Pav", "Batata Vada", "Bread Pakoda", "Paneer Pakoda", + "Veg Sandwich", "Cheese Sandwich", "Grilled Sandwich", "Corn Sandwich", "Bombay Sandwich", + "Pani Puri", "Bhel Puri", "Sev Puri", "Dahi Puri", "Aloo Tikki", + "Pav Bhaji", "Misal Pav", "Poha", "Upma", "Idli Sambhar", + "Medu Vada", "Masala Dosa", "Onion Uttapam", "Plain Dosa", "Cheese Dosa" + }, 15.0, 150.0); + + // Bakery & Sweets + addProductsToCategory("Bakery & Sweets", new String[]{ + "Chocolate Cake", "Pineapple Cake", "Black Forest Cake", "Red Velvet Cake", "Butterscotch Cake", + "Chocolate Brownie", "Walnut Brownie", "Apple Pie", "Cheesecake", "Fruit Tart", + "Gulab Jamun", "Rasgulla", "Kaju Katli", "Motichoor Ladoo", "Jalebi", + "Vanilla Muffin", "Choco Chip Cookie", "Oatmeal Cookie", "Crossiant", "Danish Pastry" + }, 40.0, 250.0); + + System.out.println("Products seeding check complete!"); + } + + private void addProductsToCategory(String category, String[] productNames, double minPrice, double maxPrice) { + java.util.Random random = new java.util.Random(); + List productsToSave = new java.util.ArrayList<>(); + + for (String name : productNames) { + if (!productRepository.existsByNameAndCategory(name, category)) { + double price = minPrice + (maxPrice - minPrice) * random.nextDouble(); + price = Math.round(price / 5.0) * 5.0; // Round to nearest 5 + + int stock = random.nextInt(100); + boolean isVeg = !name.toLowerCase().contains("chicken") && + !name.toLowerCase().contains("meat") && + !name.toLowerCase().contains("fish") && + !name.toLowerCase().contains("egg") && + !name.toLowerCase().contains("mutton") && + !name.toLowerCase().contains("tuna"); + + Product p = createProduct(name, category, name + " prepared fresh with quality ingredients.", price, stock, isVeg); + p.setProductId("PRD-" + String.format("%04d", random.nextInt(10000))); + productsToSave.add(p); + } + } + if (!productsToSave.isEmpty()) { + productRepository.saveAll(productsToSave); + System.out.println("Seeded " + productsToSave.size() + " new products in " + category); + } } private Product createProduct(String name, String category, String desc, Double price, Integer stock, boolean isVeg) { diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 9dabc2ea..a33d933f 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -13,3 +13,7 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # Master Account Credentials app.master.username=admin app.master.password=admin + +# File upload configuration +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB diff --git a/frontend/src/pages/BaseMenu.tsx b/frontend/src/pages/BaseMenu.tsx index 465e4367..b7ca69fd 100644 --- a/frontend/src/pages/BaseMenu.tsx +++ b/frontend/src/pages/BaseMenu.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { Plus, X, Search, Filter, MoreVertical, RefreshCw, Edit2, Power, PowerOff, ShoppingCart, Package, ExternalLink } from 'lucide-react'; +import Pagination from '../components/Pagination'; interface BaseItem { id: number; @@ -23,6 +24,12 @@ const BaseMenu = () => { const [editingItem, setEditingItem] = useState(null); const [newItem, setNewItem] = useState({ name: '', description: '', active: true }); const [openMenuId, setOpenMenuId] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + + // Pagination States + const [currentPage, setCurrentPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [totalElements, setTotalElements] = useState(0); // Associated Products State const [selectedBaseItem, setSelectedBaseItem] = useState(null); @@ -42,14 +49,21 @@ const BaseMenu = () => { }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); + }, [currentPage, pageSize]); const fetchItems = async () => { setLoading(true); try { - const response = await fetch('http://localhost:8080/api/base-items'); + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/base-items?page=${currentPage}&size=${pageSize}`); const data = await response.json(); - setItems(data); + if (data && data.content) { + setItems(data.content); + setTotalElements(data.totalElements); + } else { + setItems([]); + setTotalElements(0); + } } catch (error) { console.error('Error fetching items:', error); } finally { @@ -147,6 +161,8 @@ const BaseMenu = () => { setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-gray-50 border border-[#e2e8f0] rounded-lg text-sm focus:outline-none focus:border-[#231651]/30 transition-all" /> @@ -161,7 +177,7 @@ const BaseMenu = () => { {/* Table */} -
+
@@ -184,7 +200,7 @@ const BaseMenu = () => { - ) : items.length === 0 ? ( + ) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? ( ) : ( - items.map((item) => ( - + items + .filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())) + .map((item) => ( +

No items found

@@ -192,8 +208,10 @@ const BaseMenu = () => {
#{item.id} {item.name} @@ -263,6 +281,16 @@ const BaseMenu = () => {
+ { + setPageSize(newSize); + setCurrentPage(0); + }} + />
{/* Associated Products Modal */} diff --git a/frontend/src/pages/Products.tsx b/frontend/src/pages/Products.tsx index dae8f552..5f9ce1ef 100644 --- a/frontend/src/pages/Products.tsx +++ b/frontend/src/pages/Products.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { Plus, X, Search, Filter, MoreVertical, RefreshCw, Edit2, Power, PowerOff, Tag, Package, Image as ImageIcon, Barcode, DollarSign, ChevronDown, Clock, Check, Trash2, Database } from 'lucide-react'; +import Pagination from '../components/Pagination'; interface ProductSession { id?: number; @@ -87,6 +88,13 @@ const Products = () => { const [formData, setFormData] = useState(emptyProduct); const [allStalls, setAllStalls] = useState([]); const [openMenuId, setOpenMenuId] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + + // Pagination States + const [currentPage, setCurrentPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [totalElements, setTotalElements] = useState(0); + const menuRef = useRef(null); const fileInputRef = useRef(null); @@ -101,14 +109,21 @@ const Products = () => { }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); + }, [currentPage, pageSize]); const fetchProducts = async () => { setLoading(true); try { - const response = await fetch('http://localhost:8080/api/products'); + const host = window.location.hostname; + const response = await fetch(`http://${host}:8080/api/products?page=${currentPage}&size=${pageSize}`); const data = await response.json(); - setProducts(data); + if (data && data.content) { + setProducts(data.content); + setTotalElements(data.totalElements); + } else { + setProducts([]); + setTotalElements(0); + } } catch (error) { console.error('Error fetching products:', error); } finally { @@ -260,6 +275,8 @@ const Products = () => { setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-gray-50 border border-[#e2e8f0] rounded-lg text-sm focus:outline-none focus:border-[#231651]/30 transition-all" />
@@ -270,7 +287,7 @@ const Products = () => { {/* Table */} -
+
@@ -285,8 +302,10 @@ const Products = () => { {loading ? ( - - ) : products.map((product) => ( + + ) : products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.category.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? ( + + ) : products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.category.toLowerCase().includes(searchTerm.toLowerCase())).map((product) => (
Loading...
Loading...
No products found
@@ -370,6 +389,16 @@ const Products = () => {
+ { + setPageSize(newSize); + setCurrentPage(0); + }} + />
{/* Product Modal */}