Counter Module initialized
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user