Counter Module initialized

This commit is contained in:
Sidharth Prabhu
2026-04-09 19:22:52 +05:30
parent 028b5d706a
commit 3d6a57593d
8 changed files with 194 additions and 41 deletions

View File

@@ -17,8 +17,10 @@ public class BaseItemController {
private BaseItemRepository baseItemRepository;
@GetMapping
public List<BaseItem> getAllBaseItems() {
return baseItemRepository.findAll();
public org.springframework.data.domain.Page<BaseItem> getAllBaseItems(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return baseItemRepository.findAll(org.springframework.data.domain.PageRequest.of(page, size));
}
@PostMapping

View File

@@ -17,8 +17,10 @@ public class ProductController {
private ProductRepository productRepository;
@GetMapping
public List<Product> getAllProducts() {
return productRepository.findAllWithStalls();
public org.springframework.data.domain.Page<Product> 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}")

View File

@@ -6,4 +6,5 @@ import org.springframework.stereotype.Repository;
@Repository
public interface BaseItemRepository extends JpaRepository<BaseItem, Long> {
boolean existsByName(String name);
}

View File

@@ -9,8 +9,11 @@ import java.util.List;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls")
List<Product> 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<Product> findAllWithStalls(org.springframework.data.domain.Pageable pageable);
List<Product> findByCategory(String category);
boolean existsByNameAndCategory(String name, String category);
}

View File

@@ -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<Product> 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) {

View File

@@ -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

View File

@@ -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<BaseItem | null>(null);
const [newItem, setNewItem] = useState({ name: '', description: '', active: true });
const [openMenuId, setOpenMenuId] = useState<number | null>(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<BaseItem | null>(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 = () => {
<input
type="text"
placeholder="Search items..."
value={searchTerm}
onChange={(e) => 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"
/>
</div>
@@ -161,7 +177,7 @@ const BaseMenu = () => {
</div>
{/* Table */}
<div className="bg-white rounded-xl border border-[#e2e8f0] shadow-sm overflow-hidden">
<div className="bg-white rounded-xl border border-[#e2e8f0] shadow-sm overflow-hidden mb-6">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
@@ -184,7 +200,7 @@ const BaseMenu = () => {
</div>
</td>
</tr>
) : items.length === 0 ? (
) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).length === 0 ? (
<tr>
<td colSpan={6} className="px-6 py-12 text-center text-[#64748b]">
<p className="text-lg font-medium mb-1">No items found</p>
@@ -192,8 +208,10 @@ const BaseMenu = () => {
</td>
</tr>
) : (
items.map((item) => (
<tr key={item.id} className="hover:bg-gray-50/50 transition-all group">
items
.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
.map((item) => (
<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">
<span className="text-sm font-bold text-[#1e293b]">{item.name}</span>
@@ -263,6 +281,16 @@ const BaseMenu = () => {
</tbody>
</table>
</div>
<Pagination
currentPage={currentPage}
pageSize={pageSize}
totalElements={totalElements}
onPageChange={setCurrentPage}
onPageSizeChange={(newSize) => {
setPageSize(newSize);
setCurrentPage(0);
}}
/>
</div>
{/* Associated Products Modal */}

View File

@@ -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<Product>(emptyProduct);
const [allStalls, setAllStalls] = useState<Stall[]>([]);
const [openMenuId, setOpenMenuId] = useState<number | null>(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<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(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 = () => {
<input
type="text"
placeholder="Search products..."
value={searchTerm}
onChange={(e) => 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"
/>
</div>
@@ -270,7 +287,7 @@ const Products = () => {
</div>
{/* Table */}
<div className="bg-white rounded-xl border border-[#e2e8f0] shadow-sm">
<div className="bg-white rounded-xl border border-[#e2e8f0] shadow-sm mb-6">
<div className="overflow-x-visible">
<table className="w-full text-left border-collapse">
<thead>
@@ -285,8 +302,10 @@ const Products = () => {
</thead>
<tbody className="divide-y divide-[#e2e8f0]">
{loading ? (
<tr><td colSpan={5} className="px-6 py-12 text-center text-[#64748b]"><RefreshCw className="animate-spin inline mr-2" />Loading...</td></tr>
) : products.map((product) => (
<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 ? (
<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) => (
<tr key={product.id} className="hover:bg-gray-50/50 transition-all">
<td className="px-6 py-4">
<div className="flex items-center gap-3">
@@ -370,6 +389,16 @@ const Products = () => {
</tbody>
</table>
</div>
<Pagination
currentPage={currentPage}
pageSize={pageSize}
totalElements={totalElements}
onPageChange={setCurrentPage}
onPageSizeChange={(newSize) => {
setPageSize(newSize);
setCurrentPage(0);
}}
/>
</div>
{/* Product Modal */}