vkashti / app / menu / page.tsx
page.tsx
Raw
'use client';

import { FC, useState, useEffect, useMemo, useCallback, useRef } from 'react';
import menuDataRaw from './menu.json';
import ShoppingCart from '../components/ShoppingCart';
import { createClient } from '@/utils/supabase/client';
import Confetti from 'react-confetti';

interface Article {
  article_id: number;
  article_name: string;
  actual_price?: number | null;
}

interface Category {
  cat_id: number;
  parent_id: number | null;
  cat_name: string;
  articles?: Article[];
}

interface CartItem extends Article {
  quantity: number;
}

const menuData: Category[] = menuDataRaw as Category[];

const BarMenu: FC = () => {
  const [activeCategory, setActiveCategory] = useState<number | null>(null);
  const categoryRefs = useRef<{[key: number]: HTMLDivElement | null}>({});
  const observerRef = useRef<IntersectionObserver | null>(null);
  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 [showBillDialog, setShowBillDialog] = useState(false);
  const [paymentMethod, setPaymentMethod] = useState<'cash' | 'card'>('cash');
  const [billRequestStatus, setBillRequestStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [billRequestError, setBillRequestError] = useState<string | null>(null);
  const [submittedOrder, setSubmittedOrder] = useState<{
    cart: CartItem[];
    totalPrice: number;
    orderNote: string;
  } | null>(null);
  const [showConfetti, setShowConfetti] = useState(false);
  const [confettiPosition, setConfettiPosition] = useState({ x: 0, y: 0 });

  // Handle table parameter from URL
  useEffect(() => {
    const searchParams = new URLSearchParams(window.location.search);
    const tableIdFromUrl = searchParams.get('table');
    
    if (tableIdFromUrl) {
      setTableId(tableIdFromUrl);
      // Fetch table information from the database
      const fetchTableInfo = async () => {
        try {
          const supabase = createClient();
          const { data, error } = await supabase
            .from('tables')
            .select('label')
            .eq('id', parseInt(tableIdFromUrl, 10))
            .single();
          
          if (error) throw error;
          
          // Store table info in localStorage with expiration
          const tableInfo = {
            id: tableIdFromUrl,
            name: data?.label || `Маса ${tableIdFromUrl}`, // Use table label or fallback to "Маса {id}"
            expiresAt: new Date().getTime() + (24 * 60 * 60 * 1000) // 24 hours from now
          };
          localStorage.setItem('tableInfo', JSON.stringify(tableInfo));
          setTableName(tableInfo.name);
        } catch (error) {
          console.error('Error fetching table info:', error);
          // Fallback to default naming
          const tableInfo = {
            id: tableIdFromUrl,
            name: `Маса ${tableIdFromUrl}`,
            expiresAt: new Date().getTime() + (24 * 60 * 60 * 1000)
          };
          localStorage.setItem('tableInfo', JSON.stringify(tableInfo));
          setTableName(tableInfo.name);
        }
      };
      
      fetchTableInfo();
    }
  }, []);

  // Check if there's table information in localStorage and if it's still valid
  useEffect(() => {
    const tableInfoString = localStorage.getItem('tableInfo');
    
    if (tableInfoString) {
      try {
        const tableInfo = JSON.parse(tableInfoString);
        const now = new Date().getTime();
        
        // Check if the table info has expired
        if (tableInfo.expiresAt > now) {
          setTableName(tableInfo.name);
          setTableId(tableInfo.id);
        } else {
          // Clear expired table info
          localStorage.removeItem('tableInfo');
        }
      } catch (error) {
        console.error('Error parsing table info:', error);
        localStorage.removeItem('tableInfo');
      }
    }
  }, []);

  // Set first top-level category as active by default
  useEffect(() => {
    const firstCategory = menuData.find(c => c.parent_id === null);
    if (firstCategory) {
      setActiveCategory(firstCategory.cat_id);
    }
  }, []);

  // Get top-level categories for navigation
  const topLevelCategories = useMemo(() => 
    menuData.filter(c => c.parent_id === null), 
  []);

  // Scroll to category when clicking on navigation
  const scrollToCategory = (categoryId: number) => {
    setActiveCategory(categoryId);
    const element = categoryRefs.current[categoryId];
    if (element) {
      // Scroll with offset to account for sticky header
      const headerHeight = 60; // Approximate height of the navigation header
      const elementPosition = element.getBoundingClientRect().top + window.scrollY;
      window.scrollTo({
        top: elementPosition - headerHeight,
        behavior: 'smooth'
      });
    }
  };

  // Add an item to the cart - implemented as a hook to pass to ShoppingCart
  const addToCart = useCallback((item: Article, event?: React.MouseEvent<HTMLButtonElement>) => {
    // Get the position of the clicked button or use default position (center of screen)
    if (event) {
      const buttonRect = event.currentTarget.getBoundingClientRect();
      setConfettiPosition({
        x: buttonRect.left + buttonRect.width / 2,
        y: buttonRect.top + buttonRect.height / 2
      });
    } else {
      // Default to center bottom of screen if no event 
      setConfettiPosition({
        x: window.innerWidth / 2,
        y: window.innerHeight - 100
      });
    }
    
    const storedCart = localStorage.getItem('cart');
    let currentCart: CartItem[] = storedCart ? JSON.parse(storedCart) : [];
    
    // Check if the item is already in the cart
    const existingItemIndex = currentCart.findIndex(cartItem => cartItem.article_id === item.article_id);
    
    if (existingItemIndex > -1) {
      // Increase quantity if item exists
      currentCart[existingItemIndex].quantity += 1;
    } else {
      // Add new item with quantity 1
      currentCart.push({
        ...item,
        quantity: 1
      });
    }
    
    // Update localStorage
    localStorage.setItem('cart', JSON.stringify(currentCart));
    
    // Dispatch custom event for components in the same window
    window.dispatchEvent(new CustomEvent('cartUpdated', { 
      detail: { cart: currentCart }
    }));
    
    // Show confetti
    setShowConfetti(true);
    setTimeout(() => setShowConfetti(false), 2000);
    
    console.log('Item added to cart:', item);
  }, []);

  // 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 bill request submission
  const handleBillRequest = async () => {
    try {
      // Set loading state
      setBillRequestStatus('loading');
      
      const response = await fetch('/api/bill', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          tableName: tableName,
          tableId: tableId,
          paymentMethod: paymentMethod
        })
      });

      if (response.ok) {
        // Set success state
        setBillRequestStatus('success');
        setBillRequestError(null);
        
        // Close the dialog after a delay to show success message
        setTimeout(() => {
          setShowBillDialog(false);
          setBillRequestStatus('idle');
        }, 3000);
      } else {
        const data = await response.json();
        // Set error state with message
        setBillRequestStatus('error');
        setBillRequestError(data.message || 'Възникна проблем при изпращане на искането за сметка.');
      }
    } catch (error) {
      console.error('Error requesting bill:', error);
      // Set error state with generic message
      setBillRequestStatus('error');
      setBillRequestError('Възникна грешка при изпращането на искането за сметка. Моля, опитайте отново.');
    }
  };

  // Render categories and items
  const renderCategoriesAndItems = useCallback(
    (parentId: number | null, depth: number = 0): JSX.Element[] => {
      const categories = menuData.filter((c) => c.parent_id === parentId);

      return categories
        .map((category) => {
          const items = category.articles || [];
          const subElements = renderCategoriesAndItems(category.cat_id, depth + 1);

          if (items.length === 0 && subElements.length === 0) return null;

          return (
            <div
              key={category.cat_id}
              ref={(el) => {
                if (depth === 0) {
                  categoryRefs.current[category.cat_id] = el;
                }
              }}
              className={`py-4 ${
                depth === 0 ? 'border-b mb-6' : 'border-l pl-4 ml-2'
              }`}
              id={`category-${category.cat_id}`}
            >
              <h2
                className={`font-bold ${
                  depth === 0 ? 'text-xl mb-4' : 'text-lg my-2'
                }`}
              >
                {category.cat_name}
              </h2>
              
              {items.length > 0 && (
                <ul className="mb-2 divide-y">
                  {items.map((item) => (
                    <li
                      key={item.article_id}
                      className="flex justify-between py-2 items-center hover:bg-orange-100 rounded-md px-2"
                    >
                      <span className="font-medium">{item.article_name}</span>
                      <div className="flex items-center gap-2">
                        {item.actual_price !== undefined && item.actual_price !== null && (
                          <span className="font-medium">
                            {item.actual_price.toFixed(2)} лв
                          </span>
                        )}
                        {tableName && (
                          <button
                            onClick={(e) => addToCart(item, e)}
                            className="btn btn-sm btn-ghost border-2 border-orange-500 text-sm px-3 py-1 rounded-full transition-colors"
                            aria-label="Добави в поръчката"
                          >
                            +
                          </button>
                        )}
                      </div>
                    </li>
                  ))}
                </ul>
              )}
              {subElements}
            </div>
          );
        })
        .filter(Boolean) as JSX.Element[];
    },
    [addToCart, tableName]
  );

  const menuContent = useMemo(() => renderCategoriesAndItems(null), [renderCategoriesAndItems]);

  // Set up intersection observer to detect which category is currently visible
  useEffect(() => {
    const options = {
      root: null,
      rootMargin: "-80px 0px 0px 0px", // Adjust based on header height
      threshold: 0.1
    };

    const handleIntersect = (entries: IntersectionObserverEntry[]) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const categoryId = Number(entry.target.id.replace('category-', ''));
          setActiveCategory(categoryId);
        }
      });
    };

    observerRef.current = new IntersectionObserver(handleIntersect, options);
    
    // Observe all category elements
    Object.values(categoryRefs.current).forEach(ref => {
      if (ref) observerRef.current?.observe(ref);
    });

    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect();
      }
    };
  }, [menuContent]); // Add menuContent as dependency so it runs after categories are rendered

  return (
    <div className="max-w-4xl mx-auto pb-6 relative">
      {/* Table info and Bill Request Button */}
      {tableName && (
        <div className="flex justify-between items-center px-4 py-2 md:px-6 lg:px-8 bg-white shadow-sm rounded-lg mb-2">
          <div className="flex items-center">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-amber-700" 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>
            <span className="font-medium text-amber-800">Маса: {tableName}</span>
          </div>
          <button 
            onClick={() => setShowBillDialog(true)}
            className="btn btn-sm btn-primary text-white px-4 py-1 rounded-lg transition-colors flex items-center gap-1"
          >
            <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2z" />
            </svg>
            Поискай сметка
          </button>
        </div>
      )}

      {/* Categories navigation */}
      <div className="sticky top-20 sm:top-10 bg-white z-30 shadow-md w-full rounded-lg">
        <div className="max-w-4xl mx-auto px-4 md:px-6 lg:px-8">
          <div className="overflow-x-auto py-4 no-scrollbar">
            <div className="flex gap-2 items-center">
              {topLevelCategories.map((category) => (
                <button 
                  key={category.cat_id}
                  onClick={() => scrollToCategory(category.cat_id)}
                  className={`whitespace-nowrap px-4 py-2 rounded-full text-sm font-medium transition-colors ${
                    activeCategory === category.cat_id 
                      ? 'border-orange-500 border' 
                      : 'bg-gray-100 text-gray-800 hover:bg-gray-200'
                  }`}
                >
                  {category.cat_name}
                </button>
              ))}
            </div>
          </div>
        </div>
      </div>

      <div className="px-4 md:px-6 lg:px-8 pt-4">
        {menuContent}
      </div>

      {tableName && (
        <ShoppingCart 
          addToCart={addToCart}
          onOrderSubmit={handleOrderSubmit}
        />
      )}

      {/* Bill Request Dialog */}
      {showBillDialog && (
        <div className="fixed inset-0 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 p-6 rounded-lg">
              {billRequestStatus === 'idle' || billRequestStatus === 'loading' ? (
                <>
                  <h3 className="text-xl font-bold text-center mb-4">Поискай сметка</h3>
                  
                  {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="mb-4">
                    <h4 className="font-semibold mb-2">Изберете начин на плащане:</h4>
                    <div className="flex gap-2 justify-center">
                      <button
                        onClick={() => setPaymentMethod('cash')}
                        className={`flex-1 py-3 px-4 rounded-lg flex flex-col items-center justify-center border-2 transition-colors ${
                          paymentMethod === 'cash' 
                            ? 'bg-green-50 border-green-500 text-green-700' 
                            : 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50'
                        }`}
                      >
                        <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2z" />
                        </svg>
                        <span className="font-medium">В брой</span>
                      </button>
                      <button
                        onClick={() => setPaymentMethod('card')}
                        className={`flex-1 py-3 px-4 rounded-lg flex flex-col items-center justify-center border-2 transition-colors ${
                          paymentMethod === 'card' 
                            ? 'bg-blue-50 border-blue-500 text-blue-700' 
                            : 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50'
                        }`}
                      >
                        <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
                        </svg>
                        <span className="font-medium">Карта</span>
                      </button>
                    </div>
                  </div>
                  
                  <div className="flex justify-between gap-2">
                    <button 
                      onClick={() => setShowBillDialog(false)}
                      className="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded"
                    >
                      Отказ
                    </button>
                    <button 
                      onClick={handleBillRequest}
                      disabled={billRequestStatus === 'loading'}
                      className={`flex-1 ${billRequestStatus === 'loading' ? 'bg-gray-400' : 'bg-green-600 hover:bg-green-700'} text-white py-2 px-4 rounded flex justify-center items-center`}
                    >
                      {billRequestStatus === 'loading' ? (
                        <>
                          <svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                            <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                            <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                          </svg>
                          Изпращане...
                        </>
                      ) : (
                        'Изпрати'
                      )}
                    </button>
                  </div>
                </>
              ) : billRequestStatus === 'success' ? (
                <div className="text-center">
                  <div className="flex justify-center mb-4">
                    <div className="rounded-full bg-green-100 p-3">
                      <svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                      </svg>
                    </div>
                  </div>
                  <h3 className="text-xl font-bold text-center mb-4">Сметката е поискана успешно!</h3>
                  <p className="text-gray-600 mb-6">Вашата сметка ще бъде донесена скоро.</p>
                  <div className="text-center">
                    <p className="text-sm text-gray-500">Този прозорец ще се затвори автоматично</p>
                  </div>
                </div>
              ) : (
                <div className="text-center">
                  <div className="flex justify-center mb-4">
                    <div className="rounded-full bg-red-100 p-3">
                      <svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                      </svg>
                    </div>
                  </div>
                  <h3 className="text-xl font-bold text-center mb-4">Възникна грешка</h3>
                  <p className="text-gray-600 mb-6">{billRequestError}</p>
                  <button 
                    onClick={() => {
                      setBillRequestStatus('idle');
                      setBillRequestError(null);
                    }}
                    className="bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded"
                  >
                    Опитайте отново
                  </button>
                </div>
              )}
            </div>
          </div>
        </div>
      )}

      {/* Order Confirmation 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>
                <p className="text-xs text-amber-700 mt-2">Запазете кода за справка</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>
      )}

      {/* Confetti Animation for adding items */}
      {showConfetti && (
        <div className="fixed inset-0 pointer-events-none">
          <Confetti
            width={window.innerWidth}
            height={window.innerHeight}
            colors={['#f59e0b', '#d97706', '#fdba74', '#fbbf24', '#f97316']}
            recycle={false}
            numberOfPieces={100}
            gravity={0.3}
            confettiSource={{
              x: confettiPosition.x,
              y: confettiPosition.y,
              w: 0,
              h: 0
            }}
            initialVelocityX={10}
            initialVelocityY={10}
            tweenDuration={100}
          />
        </div>
      )}
    </div>
  );
};

export default BarMenu;