'use client';
import { Suspense } from 'react';
import { FC, useState, useEffect, useMemo, useCallback, useRef } from 'react';
import menuDataRaw from '../../menu/menu.json';
import AdminShoppingCart from '../../components/AdminShoppingCart';
import { createClient } from '@/utils/supabase/client';
import { FaChevronLeft } from 'react-icons/fa';
interface Article {
article_id: number;
article_name: string;
actual_price?: number | null;
}
interface Category {
cat_id: number;
cat_name: string;
parent_id: number | null;
articles?: Article[];
}
interface CartItem extends Article {
quantity: number;
}
const menuData: Category[] = menuDataRaw as Category[];
const AdminMenu: FC = () => {
const [activeCategory, setActiveCategory] = useState<number | null>(null);
const [activeCategoryParent, setActiveCategoryParent] = useState<number | null>(null);
const [categoryPath, setCategoryPath] = useState<Category[]>([]);
const [showOrderDialog, setShowOrderDialog] = useState(false);
const [orderCode, setOrderCode] = useState<string>('');
const [tableName, setTableName] = useState<string | null>(null);
const [tableId, setTableId] = useState<string | null>(null);
const [tables, setTables] = useState<any[]>([]);
const [useGridLayout, setUseGridLayout] = useState(false);
const [submittedOrder, setSubmittedOrder] = useState<{
cart: CartItem[];
totalPrice: number;
orderNote: string;
} | null>(null);
const [showTableSelection, setShowTableSelection] = useState(true);
// Fetch tables
useEffect(() => {
const fetchTables = async () => {
const supabase = createClient();
try {
// Get basic table data first
const { data, error } = await supabase
.from('tables')
.select('id, label, position_x, position_y, capacity')
.order('label');
if (error) {
console.error('Error fetching tables:', error);
return;
}
// Check if any tables have valid position data
const hasValidPositions = data.some(table =>
table &&
typeof table.position_x === 'number' &&
typeof table.position_y === 'number' &&
!isNaN(table.position_x) &&
!isNaN(table.position_y)
);
// If no valid positions, use grid layout
setUseGridLayout(!hasValidPositions);
// Apply default width and height to all tables
const processedData = data.map((table, index) => {
// Generate default position if none exists
const position_x = (typeof table.position_x === 'number' && !isNaN(table.position_x))
? table.position_x
: (index % 4) * 100 + 50;
const position_y = (typeof table.position_y === 'number' && !isNaN(table.position_y))
? table.position_y
: Math.floor(index / 4) * 100 + 50;
return {
...table,
width: 60,
height: 60,
position_x,
position_y
};
});
console.log('Tables data:', processedData);
setTables(processedData);
} catch (error) {
console.error('Error in tables fetch:', error);
}
};
fetchTables();
}, []);
// Function to select a table and proceed to menu
const handleTableSelect = (id: number, name: string) => {
setTableId(id.toString());
setTableName(name);
setShowTableSelection(false);
// Reset cart and local storage
localStorage.setItem('cart', JSON.stringify([]));
window.dispatchEvent(new CustomEvent('cartUpdated', {
detail: { cart: [] }
}));
};
// Add item to cart with position tracking for animation
const addToCart = useCallback((item: Article, event?: React.MouseEvent<HTMLButtonElement>) => {
// Get current cart
const currentCart = JSON.parse(localStorage.getItem('cart') || '[]');
// Check if item is already in cart
const existingItemIndex = currentCart.findIndex(
(cartItem: CartItem) => cartItem.article_id === item.article_id
);
let newCart;
if (existingItemIndex >= 0) {
// Increment quantity if item exists
newCart = currentCart.map((cartItem: CartItem, index: number) => {
if (index === existingItemIndex) {
return { ...cartItem, quantity: cartItem.quantity + 1 };
}
return cartItem;
});
} else {
// Add new item to cart
newCart = [...currentCart, { ...item, quantity: 1 }];
}
// Save to localStorage
localStorage.setItem('cart', JSON.stringify(newCart));
// Dispatch custom event to update cart in all components
window.dispatchEvent(new CustomEvent('cartUpdated', {
detail: { cart: newCart }
}));
}, []);
// Get top-level categories
const topLevelCategories = useMemo(
() => menuData.filter((category) => category.parent_id === activeCategoryParent),
[menuData, activeCategoryParent]
);
// Handle order submission
const handleOrderSubmit = async (orderData: { cart: CartItem[], orderNote: string, totalPrice: number }) => {
console.log('Order submitted:', orderData);
try {
// Send order to API endpoint with table information if available
const response = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...orderData,
tableName: tableName
})
});
if (response.ok) {
const data = await response.json();
// Store the order code and submitted order
setOrderCode(data.orderCode);
setSubmittedOrder(orderData);
// Show the dialog
setShowOrderDialog(true);
// Clear the cart in localStorage
localStorage.setItem('cart', JSON.stringify([]));
// Dispatch custom event to update cart in all components
window.dispatchEvent(new CustomEvent('cartUpdated', {
detail: { cart: [] }
}));
} else {
const data = await response.json();
alert(`Грешка: ${data.message || 'Възникна проблем при изпращане на поръчката.'}`);
}
} catch (error) {
console.error('Error submitting order:', error);
alert('Възникна грешка при изпращането на поръчката. Моля, опитайте отново.');
}
};
// Handle change of category
const handleCategoryChange = (catId: number) => {
const selectedCategory = menuData.find(c => c.cat_id === catId);
if (!selectedCategory) return;
// Check if this category has subcategories
const hasSubcategories = menuData.some(c => c.parent_id === catId);
if (hasSubcategories) {
// Update path
setCategoryPath(prev => [...prev, selectedCategory]);
// Set parent to display its children
setActiveCategoryParent(catId);
// Reset active category to null to show all subcategories
setActiveCategory(null);
} else {
// If no subcategories, just set it as active
setActiveCategory(catId);
}
};
// Handle navigation back to parent category
const handleCategoryBack = () => {
if (categoryPath.length <= 1) {
// Back to top level
setCategoryPath([]);
setActiveCategoryParent(null);
setActiveCategory(null);
} else {
// Go back one level
const newPath = [...categoryPath];
const removedCategory = newPath.pop(); // Remove last item
const parentCategory = newPath[newPath.length - 1];
setCategoryPath(newPath);
// If we're going back to the top level
if (newPath.length === 0) {
setActiveCategoryParent(null);
} else {
// We're going back to a parent category
setActiveCategoryParent(parentCategory.cat_id);
}
setActiveCategory(null);
}
};
// Function to render menu by category
const renderMenuByCategory = useCallback(() => {
// If no active category, show all categories from current level
if (activeCategory === null) {
// Display all categories at the current level
return (
<div className="space-y-6">
{topLevelCategories.map(category => {
// Filter articles for this category
const items = category.articles || [];
// Check if category has subcategories
const hasSubcategories = menuData.some(c => c.parent_id === category.cat_id);
return (
<div key={category.cat_id} className="space-y-2">
<h2
className={`text-xl font-semibold ${hasSubcategories ? 'cursor-pointer hover:text-orange-600' : ''}`}
onClick={hasSubcategories ? () => handleCategoryChange(category.cat_id) : undefined}
>
{category.cat_name}
{hasSubcategories && (
<span className="inline-block ml-2 text-sm">→</span>
)}
</h2>
{items.length > 0 && (
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{items.map((item) => (
<li
key={item.article_id}
className="flex justify-between items-center hover:bg-orange-50 rounded-md px-2 py-1.5"
>
<span className="font-medium truncate">{item.article_name}</span>
<div className="flex items-center gap-2">
{item.actual_price !== undefined && item.actual_price !== null && (
<span className="font-medium whitespace-nowrap">
{item.actual_price.toFixed(2)} лв
</span>
)}
<button
onClick={(e) => addToCart(item, e)}
className="btn btn-xs btn-ghost border border-orange-500 text-sm px-2 py-0.5 rounded-full transition-colors"
aria-label="Добави в поръчката"
>
+
</button>
</div>
</li>
))}
</ul>
)}
</div>
);
})}
</div>
);
}
// If an active category is selected, show its details
// Find selected category
const selectedCategory = menuData.find(c => c.cat_id === activeCategory);
if (!selectedCategory) return null;
// Get subcategories
const subcategories = menuData.filter(c => c.parent_id === activeCategory);
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">{selectedCategory.cat_name}</h2>
{/* Menu items for selected category */}
{selectedCategory.articles && selectedCategory.articles.length > 0 && (
<div className="space-y-2">
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{selectedCategory.articles.map((item) => (
<li
key={item.article_id}
className="flex justify-between items-center hover:bg-orange-50 rounded-md px-2 py-1.5"
>
<span className="font-medium truncate">{item.article_name}</span>
<div className="flex items-center gap-2">
{item.actual_price !== undefined && item.actual_price !== null && (
<span className="font-medium whitespace-nowrap">
{item.actual_price.toFixed(2)} лв
</span>
)}
<button
onClick={(e) => addToCart(item, e)}
className="btn btn-xs btn-ghost border border-orange-500 text-sm px-2 py-0.5 rounded-full transition-colors"
aria-label="Добави в поръчката"
>
+
</button>
</div>
</li>
))}
</ul>
</div>
)}
{/* Subcategories */}
{subcategories.length > 0 && (
<div className="space-y-4 mt-6">
{subcategories.map(subcat => {
const items = subcat.articles || [];
// Check if subcategory has its own subcategories
const hasNestedSubcategories = menuData.some(c => c.parent_id === subcat.cat_id);
return (
<div key={subcat.cat_id} className="space-y-2">
<h3
className={`text-lg font-medium text-orange-800 ${hasNestedSubcategories ? 'cursor-pointer hover:text-orange-600' : ''}`}
onClick={hasNestedSubcategories ? () => handleCategoryChange(subcat.cat_id) : undefined}
>
{subcat.cat_name}
{hasNestedSubcategories && (
<span className="inline-block ml-2 text-sm">→</span>
)}
</h3>
{items.length > 0 && (
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{items.map((item) => (
<li
key={item.article_id}
className="flex justify-between items-center hover:bg-orange-50 rounded-md px-2 py-1.5"
>
<span className="font-medium truncate">{item.article_name}</span>
<div className="flex items-center gap-2">
{item.actual_price !== undefined && item.actual_price !== null && (
<span className="font-medium whitespace-nowrap">
{item.actual_price.toFixed(2)} лв
</span>
)}
<button
onClick={(e) => addToCart(item, e)}
className="btn btn-xs btn-ghost border border-orange-500 text-sm px-2 py-0.5 rounded-full transition-colors"
aria-label="Добави в поръчката"
>
+
</button>
</div>
</li>
))}
</ul>
)}
</div>
);
})}
</div>
)}
</div>
);
}, [activeCategory, topLevelCategories, menuData, addToCart, handleCategoryChange]);
// Handle back to table selection
const handleBackToTableSelection = () => {
setShowTableSelection(true);
setTableId(null);
setTableName(null);
};
// Render the table selection screen
const renderTableSelection = () => {
console.log('Rendering tables:', tables);
console.log('Tables count:', tables.length);
// Fall back to grid layout if needed
const displayTables = useGridLayout
? tables.map((table, index) => ({
...table,
position_x: (index % 4) * 100 + 50,
position_y: Math.floor(index / 4) * 100 + 50
}))
: tables;
// Calculate canvas dimensions
const maxX = displayTables.length > 0
? Math.max(...displayTables.map(t => t.position_x || 0)) + 150
: 800;
const maxY = displayTables.length > 0
? Math.max(...displayTables.map(t => t.position_y || 0)) + 150
: 600;
// If there are still no tables to display, show a message
if (displayTables.length === 0) {
return (
<div className="text-center p-8">
<p className="text-lg text-gray-600">Няма налични маси.</p>
</div>
);
}
return (
<div>
<div className="mb-4 flex justify-between items-center">
<button
onClick={() => setUseGridLayout(!useGridLayout)}
className="px-3 py-1 text-sm bg-gray-100 hover:bg-gray-200 rounded-md"
>
{useGridLayout ? 'Реален изглед' : 'Табличен изглед'}
</button>
</div>
{useGridLayout ? (
// Simple grid layout
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
{tables.map((table) => (
<button
key={table.id}
onClick={() => handleTableSelect(table.id, table.label)}
className="flex flex-col items-center justify-center p-3 border border-amber-200 rounded-lg bg-amber-50 hover:bg-amber-100 transition-colors"
>
<span className="font-medium text-amber-800">{table.label}</span>
<span className="text-xs text-amber-600">
{table.capacity} {table.capacity === 1 ? 'място' : 'места'}
</span>
</button>
))}
</div>
) : (
// Visual layout
<div className="relative overflow-hidden">
<div
id="layout-canvas"
className="relative bg-gray-50 overflow-auto"
style={{
height: '500px'
}}
>
<div className="relative" style={{ width: `${maxX}px`, height: `${maxY}px` }}>
{displayTables.map((table) => (
<div
key={table.id}
onClick={() => handleTableSelect(table.id, table.label)}
className="absolute border border-gray-400 bg-gray-100 rounded-md text-center flex flex-col justify-center items-center cursor-pointer transition-colors hover:bg-amber-100 hover:border-amber-300"
style={{
left: `${table.position_x}px`,
top: `${table.position_y}px`,
width: `${table.width}px`,
height: `${table.height}px`
}}
>
<span className="font-medium text-sm truncate max-w-full px-2">
{table.label}
</span>
<span className="text-xs text-gray-600">
{table.capacity} {table.capacity === 1 ? 'място' : 'места'}
</span>
</div>
))}
</div>
</div>
</div>
)}
</div>
);
};
return (
<div>
{showTableSelection ? (
<div className="mx-auto">
{renderTableSelection()}
</div>
) : (
<div className="mx-auto">
{/* Table info header */}
<div className="flex justify-between items-center p-2 bg-amber-50 rounded-md mb-3">
<button
onClick={handleBackToTableSelection}
className="flex items-center gap-1 text-amber-800 hover:text-amber-600"
>
<FaChevronLeft className="text-xs" />
<span>Назад</span>
</button>
<div className="font-medium text-amber-800">Маса: {tableName}</div>
</div>
{/* Category path navigation */}
{categoryPath.length > 0 && (
<div className="mb-3 bg-gray-50 rounded-md p-2 flex items-center">
<button
onClick={handleCategoryBack}
className="mr-2 text-gray-600 hover:text-gray-900"
>
<FaChevronLeft className="text-xs" />
</button>
<div className="text-sm text-gray-600 truncate">
{categoryPath.map((cat, index) => (
<span key={cat.cat_id}>
{index > 0 && " > "}
{cat.cat_name}
</span>
))}
</div>
</div>
)}
{/* Categories navigation */}
<div className="mb-4 bg-white rounded-md">
<div className="grid grid-cols-4 sm:grid-cols-5 gap-2 p-2">
{topLevelCategories.map((category) => (
<button
key={category.cat_id}
onClick={() => handleCategoryChange(category.cat_id)}
className={`aspect-square flex flex-col items-center justify-center p-2 rounded-md text-sm font-medium transition-colors ${
activeCategory === category.cat_id ||
(activeCategory === null && category === topLevelCategories[0])
? 'bg-orange-100 text-orange-800 border-orange-300 border'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
<span className="text-center line-clamp-2">{category.cat_name}</span>
</button>
))}
</div>
</div>
{/* Menu content */}
<div className="mb-16 mx-2">
{renderMenuByCategory()}
</div>
{/* Shopping Cart */}
<AdminShoppingCart
addToCart={addToCart}
onOrderSubmit={handleOrderSubmit}
/>
{/* Order Success Dialog */}
{showOrderDialog && submittedOrder && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
<div className="modal modal-open">
<div className="modal-box relative bg-white max-w-md">
<h3 className="text-xl font-bold text-center mb-4">Поръчката е успешна!</h3>
<div className="bg-amber-100 p-4 rounded-lg mb-4 text-center">
<p className="text-sm text-amber-800">Код за поръчката</p>
<p className="text-4xl font-bold tracking-wider text-amber-900">{orderCode}</p>
</div>
{tableName && (
<div className="bg-blue-50 p-3 rounded-lg mb-4 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-blue-500 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<div>
<h4 className="font-semibold text-blue-800">Доставка на масата</h4>
<p className="text-sm text-blue-700">{tableName}</p>
</div>
</div>
)}
<div className="max-h-60 overflow-y-auto mb-4">
<h4 className="font-semibold mb-2">Поръчани продукти:</h4>
<ul className="divide-y">
{submittedOrder.cart.map((item) => (
<li key={item.article_id} className="py-2 flex justify-between">
<span>
{item.quantity}x {item.article_name}
</span>
{item.actual_price && (
<span className="font-medium">
{(item.actual_price * item.quantity).toFixed(2)} лв
</span>
)}
</li>
))}
</ul>
</div>
{submittedOrder.orderNote && (
<div className="mb-4 bg-gray-50 p-3 rounded-lg">
<h4 className="font-semibold mb-1">Бележка:</h4>
<p className="text-sm text-gray-700">{submittedOrder.orderNote}</p>
</div>
)}
<div className="flex justify-between font-bold border-t pt-3">
<span>Общо:</span>
<span>{submittedOrder.totalPrice.toFixed(2)} лв</span>
</div>
<div className="mt-6">
<button
onClick={() => setShowOrderDialog(false)}
className="w-full py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors"
>
Затвори
</button>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
};
// Wrap the export in a component that uses Suspense
export default function MenuPage() {
return (
<Suspense fallback={<></>}>
<AdminMenu />
</Suspense>
);
}