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; private BaseItemRepository baseItemRepository;
@GetMapping @GetMapping
public List<BaseItem> getAllBaseItems() { public org.springframework.data.domain.Page<BaseItem> getAllBaseItems(
return baseItemRepository.findAll(); @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return baseItemRepository.findAll(org.springframework.data.domain.PageRequest.of(page, size));
} }
@PostMapping @PostMapping

View File

@@ -17,8 +17,10 @@ public class ProductController {
private ProductRepository productRepository; private ProductRepository productRepository;
@GetMapping @GetMapping
public List<Product> getAllProducts() { public org.springframework.data.domain.Page<Product> getAllProducts(
return productRepository.findAllWithStalls(); @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return productRepository.findAllWithStalls(org.springframework.data.domain.PageRequest.of(page, size));
} }
@GetMapping("/category/{categoryName}") @GetMapping("/category/{categoryName}")

View File

@@ -6,4 +6,5 @@ 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);
} }

View File

@@ -9,8 +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") @Query(value = "SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.stalls",
List<Product> findAllWithStalls(); 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); List<Product> findByCategory(String category);
boolean existsByNameAndCategory(String name, String category);
} }

View File

@@ -28,13 +28,9 @@ public class DatabaseSeeder implements CommandLineRunner {
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
repairStallsSchema(); repairStallsSchema();
repairLobColumns(); repairLobColumns();
if (baseItemRepository.count() == 0) {
seedCategories(); seedCategories();
}
if (productRepository.count() == 0) {
seedProducts(); seedProducts();
} }
}
private void repairStallsSchema() { private void repairStallsSchema() {
System.out.println("Checking schema consistency for 'stalls' table..."); System.out.println("Checking schema consistency for 'stalls' table...");
@@ -81,27 +77,115 @@ public class DatabaseSeeder implements CommandLineRunner {
} }
private void seedCategories() { private void seedCategories() {
BaseItem breakfast = new BaseItem("Breakfast", "Morning favorites", true); String[][] categories = {
BaseItem lunch = new BaseItem("Lunch", "Hearty meals", true); {"Fast Food", "Burgers, Pizzas and more"},
BaseItem snacks = new BaseItem("Snacks", "Quick bites", true); {"Indian Main Course", "Traditional Indian meals"},
BaseItem beverages = new BaseItem("Beverages", "Refreshing drinks", true); {"Chinese", "Oriental flavors"},
BaseItem desserts = new BaseItem("Desserts", "Sweet treats", true); {"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)); for (String[] cat : categories) {
System.out.println("Categories seeded!"); if (!baseItemRepository.existsByName(cat[0])) {
baseItemRepository.save(new BaseItem(cat[0], cat[1], true));
System.out.println("Seeded category: " + cat[0]);
}
}
} }
private void seedProducts() { private void seedProducts() {
Product dosa = createProduct("Masala Dosa", "Breakfast", "Crispy dosa with potato filling", 60.0, 50, true); // Fast Food
Product poha = createProduct("Poha", "Breakfast", "Flattened rice with onions and spices", 30.0, 40, true); addProductsToCategory("Fast Food", new String[]{
Product thali = createProduct("Veg Thali", "Lunch", "Full meal with roti, sabzi, dal and rice", 120.0, 30, true); "Veg Burger", "Chicken Burger", "Cheese Burger", "Double Patty Burger", "Spicy Zinger Burger",
Product biryani = createProduct("Chicken Biryani", "Lunch", "Aromatic rice with spiced chicken", 150.0, 20, false); "Paneer Burger", "Aloha Burger", "Mushroom Burger", "Fish Burger", "BBQ Burger",
Product samosa = createProduct("Samosa", "Snacks", "Deep fried pastry with potato filling", 15.0, 100, true); "Margherita Pizza", "Farmhouse Pizza", "Peppy Paneer", "Mexican Wave", "Pepper BBQ Chicken",
Product tea = createProduct("Masala Tea", "Beverages", "Traditional Indian spiced tea", 10.0, 200, true); "Chicken Tikka Pizza", "Meat Lover's Pizza", "Veggie Paradise", "Corn & Cheese Pizza", "Double Cheese Margherita",
Product iceCream = createProduct("Vanilla Ice Cream", "Desserts", "Classic vanilla flavor", 30.0, 0, true); // Out of stock "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)); // Indian Main Course
System.out.println("Products seeded!"); 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) { 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 # Master Account Credentials
app.master.username=admin app.master.username=admin
app.master.password=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 React, { useState, useEffect, useRef } from 'react';
import { Plus, X, Search, Filter, MoreVertical, RefreshCw, Edit2, Power, PowerOff, ShoppingCart, Package, ExternalLink } from 'lucide-react'; import { Plus, X, Search, Filter, MoreVertical, RefreshCw, Edit2, Power, PowerOff, ShoppingCart, Package, ExternalLink } from 'lucide-react';
import Pagination from '../components/Pagination';
interface BaseItem { interface BaseItem {
id: number; id: number;
@@ -23,6 +24,12 @@ const BaseMenu = () => {
const [editingItem, setEditingItem] = useState<BaseItem | null>(null); const [editingItem, setEditingItem] = useState<BaseItem | null>(null);
const [newItem, setNewItem] = useState({ name: '', description: '', active: true }); const [newItem, setNewItem] = useState({ name: '', description: '', active: true });
const [openMenuId, setOpenMenuId] = useState<number | null>(null); 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 // Associated Products State
const [selectedBaseItem, setSelectedBaseItem] = useState<BaseItem | null>(null); const [selectedBaseItem, setSelectedBaseItem] = useState<BaseItem | null>(null);
@@ -42,14 +49,21 @@ 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 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(); const data = await response.json();
setItems(data); if (data && data.content) {
setItems(data.content);
setTotalElements(data.totalElements);
} else {
setItems([]);
setTotalElements(0);
}
} catch (error) { } catch (error) {
console.error('Error fetching items:', error); console.error('Error fetching items:', error);
} finally { } finally {
@@ -147,6 +161,8 @@ const BaseMenu = () => {
<input <input
type="text" type="text"
placeholder="Search items..." 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" 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> </div>
@@ -161,7 +177,7 @@ const BaseMenu = () => {
</div> </div>
{/* Table */} {/* 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"> <div className="overflow-x-auto">
<table className="w-full text-left border-collapse"> <table className="w-full text-left border-collapse">
<thead> <thead>
@@ -184,7 +200,7 @@ const BaseMenu = () => {
</div> </div>
</td> </td>
</tr> </tr>
) : items.length === 0 ? ( ) : items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())).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>
@@ -192,7 +208,9 @@ const BaseMenu = () => {
</td> </td>
</tr> </tr>
) : ( ) : (
items.map((item) => ( items
.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">
@@ -263,6 +281,16 @@ const BaseMenu = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
<Pagination
currentPage={currentPage}
pageSize={pageSize}
totalElements={totalElements}
onPageChange={setCurrentPage}
onPageSizeChange={(newSize) => {
setPageSize(newSize);
setCurrentPage(0);
}}
/>
</div> </div>
{/* Associated Products Modal */} {/* Associated Products Modal */}

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; 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 { 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 { interface ProductSession {
id?: number; id?: number;
@@ -87,6 +88,13 @@ const Products = () => {
const [formData, setFormData] = useState<Product>(emptyProduct); const [formData, setFormData] = useState<Product>(emptyProduct);
const [allStalls, setAllStalls] = useState<Stall[]>([]); const [allStalls, setAllStalls] = useState<Stall[]>([]);
const [openMenuId, setOpenMenuId] = useState<number | null>(null); 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 menuRef = useRef<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@@ -101,14 +109,21 @@ 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 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(); const data = await response.json();
setProducts(data); if (data && data.content) {
setProducts(data.content);
setTotalElements(data.totalElements);
} else {
setProducts([]);
setTotalElements(0);
}
} catch (error) { } catch (error) {
console.error('Error fetching products:', error); console.error('Error fetching products:', error);
} finally { } finally {
@@ -260,6 +275,8 @@ const Products = () => {
<input <input
type="text" type="text"
placeholder="Search products..." 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" 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> </div>
@@ -270,7 +287,7 @@ const Products = () => {
</div> </div>
{/* Table */} {/* 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"> <div className="overflow-x-visible">
<table className="w-full text-left border-collapse"> <table className="w-full text-left border-collapse">
<thead> <thead>
@@ -285,8 +302,10 @@ const Products = () => {
</thead> </thead>
<tbody className="divide-y divide-[#e2e8f0]"> <tbody className="divide-y divide-[#e2e8f0]">
{loading ? ( {loading ? (
<tr><td colSpan={5} 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.map((product) => ( ) : 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"> <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">
@@ -370,6 +389,16 @@ const Products = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
<Pagination
currentPage={currentPage}
pageSize={pageSize}
totalElements={totalElements}
onPageChange={setCurrentPage}
onPageSizeChange={(newSize) => {
setPageSize(newSize);
setCurrentPage(0);
}}
/>
</div> </div>
{/* Product Modal */} {/* Product Modal */}