vkashti / app / admin / menu / page.tsx
page.tsx
Raw
'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>
  );
}