vkashti / app / admin / deals / DealDialog.tsx
DealDialog.tsx
Raw
'use client';

import { useEffect, useState, useRef } from 'react';
import { useDeals } from './DealsProvider';
import { Deal } from '@/types/types';
import { FiEdit, FiPackage, FiTag, FiDollarSign, FiUsers, FiUser, FiCheckCircle, FiToggleRight, FiArrowUp, FiArrowDown, FiTrash, FiCheck, FiX } from 'react-icons/fi';
import TagSelector from './TagSelector';
import { useDrag, useDrop } from 'react-dnd';

// Drag item type
const BENEFIT_ITEM = 'benefit';

// Define the type for a draggable benefit item
type BenefitItemProps = {
  index: number;
  benefit: string;
  moveItem: (dragIndex: number, hoverIndex: number) => void;
  onRemove: (index: number) => void;
  onEdit: (index: number, newValue: string) => void;
};

// Benefit Item component
const BenefitItem = ({ benefit, index, moveItem, onRemove, onEdit }: BenefitItemProps) => {
  const ref = useRef<HTMLLIElement>(null);
  const [isEditing, setIsEditing] = useState(false);
  const [editValue, setEditValue] = useState(benefit);
  const inputRef = useRef<HTMLInputElement>(null);
  
  const [{ isDragging }, drag] = useDrag({
    type: BENEFIT_ITEM,
    item: { index },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
    canDrag: () => !isEditing, // Prevent dragging while editing
  });
  
  const [, drop] = useDrop({
    accept: BENEFIT_ITEM,
    hover: (draggedItem: { index: number }, monitor) => {
      if (!ref.current || isEditing) return; // Don't allow dropping while editing
      const dragIndex = draggedItem.index;
      const hoverIndex = index;
      
      // Don't replace items with themselves
      if (dragIndex === hoverIndex) return;
      
      // Determine rectangle on screen
      const hoverBoundingRect = ref.current.getBoundingClientRect();
      
      // Get vertical middle
      const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
      
      // Determine mouse position
      const clientOffset = monitor.getClientOffset();
      if (!clientOffset) return;
      
      // Get pixels to the top
      const hoverClientY = clientOffset.y - hoverBoundingRect.top;
      
      // Only perform the move when the mouse has crossed half of the item's height
      // Dragging downwards, move only when cursor is below 50%
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return;
      
      // Dragging upwards, move only when cursor is above 50%
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return;
      
      // Time to actually perform the action
      moveItem(dragIndex, hoverIndex);
      
      // Update the index for the dragged item directly to avoid flickering
      draggedItem.index = hoverIndex;
    },
  });
  
  useEffect(() => {
    // Focus the input when entering edit mode
    if (isEditing && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isEditing]);

  // Handle saving the edited benefit
  const handleSaveEdit = () => {
    if (editValue.trim() !== '') {
      onEdit(index, editValue.trim());
      setIsEditing(false);
    }
  };

  // Handle canceling the edit
  const handleCancelEdit = () => {
    setEditValue(benefit);
    setIsEditing(false);
  };

  // Handle key presses in the edit input
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      handleSaveEdit();
    } else if (e.key === 'Escape') {
      handleCancelEdit();
    }
  };

  drag(drop(ref));
  
  return (
    <li 
      ref={ref} 
      className={`flex items-center justify-between bg-gray-100 p-2 rounded mb-1 ${isDragging ? 'opacity-50' : 'opacity-100'}`}
      style={{ cursor: isEditing ? 'default' : 'move' }}
    >
      {isEditing ? (
        <div className="flex flex-1 items-center gap-2">
          <FiCheckCircle className="text-green-600 w-4 h-4 flex-shrink-0" />
          <input
            ref={inputRef}
            type="text"
            value={editValue}
            onChange={(e) => setEditValue(e.target.value)}
            onKeyDown={handleKeyDown}
            className="input input-bordered input-sm w-full bg-white"
          />
          <div className="flex gap-1">
            <button
              type="button"
              onClick={handleSaveEdit}
              className="btn btn-xs btn-circle btn-success"
              title="Save"
            >
              <FiCheck className="w-4 h-4" />
            </button>
            <button
              type="button"
              onClick={handleCancelEdit}
              className="btn btn-xs btn-circle btn-ghost"
              title="Cancel"
            >
              <FiX className="w-4 h-4" />
            </button>
          </div>
        </div>
      ) : (
        <>
          <span className="flex items-center gap-2">
            <FiCheckCircle className="text-green-600 w-4 h-4" />
            {benefit}
          </span>
          <div className="flex gap-1">
            <button
              type="button"
              onClick={() => setIsEditing(true)}
              className="btn btn-ghost btn-xs btn-circle"
              title="Edit"
            >
              <FiEdit className="w-3 h-3" />
            </button>
            <button
              type="button"
              onClick={() => onRemove(index)}
              className="btn btn-ghost btn-xs btn-circle text-red-500"
              title="Remove"
            >
              <FiTrash className="w-3 h-3" />
            </button>
          </div>
        </>
      )}
    </li>
  );
};

export default function DealDialog() {
  const { 
    isDialogOpen, 
    setIsDialogOpen, 
    selectedDeal, 
    updateDeal,
    deleteDeal,
    updateDealTags,
    getDealTags
  } = useDeals();

  const [formData, setFormData] = useState<Partial<Deal>>({
    title: '',
    description: '',
    icon: '',
    price: 0,
    regular_price: 0,
    benefits: [],
    min_group_size: 1,
    max_group_size: 10,
    for_who: '',
    is_for_business: false
  });

  const [newBenefit, setNewBenefit] = useState('');
  const [benefitsList, setBenefitsList] = useState<string[]>([]);
  const [selectedTagIds, setSelectedTagIds] = useState<number[]>([]);
  const [isLoadingTags, setIsLoadingTags] = useState(false);
  const initialTagLoadDone = useRef(false);

  useEffect(() => {
    if (selectedDeal) {
      setFormData({
        title: selectedDeal.title || '',
        description: selectedDeal.description || '',
        icon: selectedDeal.icon || '',
        price: selectedDeal.price || 0,
        regular_price: selectedDeal.regular_price || 0,
        benefits: selectedDeal.benefits || [],
        min_group_size: selectedDeal.min_group_size || 1,
        max_group_size: selectedDeal.max_group_size || 10,
        for_who: selectedDeal.for_who || '',
        is_for_business: selectedDeal.is_for_business || false
      });
      setBenefitsList(selectedDeal.benefits || []);
      initialTagLoadDone.current = false;
      
      // Load existing tags for this deal
      if (selectedDeal.id) {
        setIsLoadingTags(true);
        getDealTags(selectedDeal.id)
          .then(tagIds => {
            console.log("Initial tags loaded in DealDialog:", tagIds);
            setSelectedTagIds(tagIds);
            initialTagLoadDone.current = true;
          })
          .catch(error => {
            console.error('Error loading tags for deal:', error);
          })
          .finally(() => {
            setIsLoadingTags(false);
          });
      }
    } else {
      // Reset state for a new deal
      setSelectedTagIds([]);
      initialTagLoadDone.current = false;
    }
  }, [selectedDeal?.id]);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    const { name, value, type } = e.target;
    
    if (type === 'checkbox') {
      setFormData({
        ...formData,
        [name]: (e.target as HTMLInputElement).checked
      });
    } else if (type === 'number') {
      setFormData({
        ...formData,
        [name]: parseFloat(value)
      });
    } else {
      setFormData({
        ...formData,
        [name]: value
      });
    }
  };

  const handleAddBenefit = () => {
    if (newBenefit.trim()) {
      const updatedBenefits = [...benefitsList, newBenefit.trim()];
      setBenefitsList(updatedBenefits);
      setFormData({
        ...formData,
        benefits: updatedBenefits
      });
      setNewBenefit('');
    }
  };

  const handleRemoveBenefit = (index: number) => {
    const updatedBenefits = benefitsList.filter((_, i) => i !== index);
    setBenefitsList(updatedBenefits);
    setFormData({
      ...formData,
      benefits: updatedBenefits
    });
  };

  // Move benefit item from one position to another
  const moveBenefitItem = (dragIndex: number, hoverIndex: number) => {
    const dragItem = benefitsList[dragIndex];
    const newBenefitsList = [...benefitsList];
    newBenefitsList.splice(dragIndex, 1);
    newBenefitsList.splice(hoverIndex, 0, dragItem);
    
    setBenefitsList(newBenefitsList);
    setFormData({
      ...formData,
      benefits: newBenefitsList
    });
  };

  const handleTagsChange = (tagIds: number[]) => {
    

    setSelectedTagIds(tagIds);
  };

  // Function to edit a benefit
  const handleEditBenefit = (index: number, newValue: string) => {
    const updatedBenefits = [...benefitsList];
    updatedBenefits[index] = newValue;
    
    setBenefitsList(updatedBenefits);
    setFormData({
      ...formData,
      benefits: updatedBenefits
    });
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    if (selectedDeal && selectedDeal.id) {
      try {
        console.log("Submitting deal with tags:", selectedTagIds);
        
        // First update the deal basic information
        await updateDeal(selectedDeal.id, formData);
        
        // Then update the tags
        if (initialTagLoadDone.current) {
          await updateDealTags(selectedDeal.id, selectedTagIds);
        }
        
        // Close the dialog after successful update
        setIsDialogOpen(false);
      } catch (error) {
        console.error('Error updating deal and tags:', error);
        alert('Failed to update deal. Please try again.');
      }
    }
  };

  const handleDelete = () => {
    if (selectedDeal && selectedDeal.id && confirm('Are you sure you want to delete this deal?')) {
      deleteDeal(selectedDeal.id);
      setIsDialogOpen(false);
    }
  };

  if (!isDialogOpen) return null;

  return (
    <dialog className="modal modal-open">
      <div className="modal-box w-11/12 max-w-2xl bg-white p-0 flex flex-col max-h-[90vh]">
        <h3 className="font-bold text-xl flex items-center gap-2 bg-white p-6 border-b sticky top-0">
          {selectedDeal?.id ? (
            <>
              <FiEdit className="w-5 h-5" />
              Edit Deal
            </>
          ) : (
            <>
              <FiPackage className="w-5 h-5" />
              New Deal
            </>
          )}
        </h3>

        <form onSubmit={handleSubmit} className="flex flex-col flex-1">
          <div className="space-y-6 p-6 overflow-y-auto flex-1">
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <div className="space-y-4">
                <div>
                  <label className="label">
                    <span className="label-text font-medium">Title</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiTag className="w-4 h-4 opacity-70" />
                    <input
                      type="text"
                      name="title"
                      value={formData.title}
                      onChange={handleInputChange}
                      className="grow"
                      required
                    />
                  </label>
                </div>

                <div>
                  <label className="label">
                    <span className="label-text font-medium">Icon (CSS class)</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiPackage className="w-4 h-4 opacity-70" />
                    <input
                      type="text"
                      name="icon"
                      value={formData.icon}
                      onChange={handleInputChange}
                      className="grow"
                    />
                  </label>
                </div>

                <div>
                  <label className="label">
                    <span className="label-text font-medium">Price (BGN)</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiDollarSign className="w-4 h-4 opacity-70" />
                    <input
                      type="number"
                      name="price"
                      value={formData.price}
                      onChange={handleInputChange}
                      className="grow"
                      min="0"
                      step="0.01"
                      required
                    />
                  </label>
                </div>

                <div>
                  <label className="label">
                    <span className="label-text font-medium">Regular Price (BGN)</span>
                    <span className="label-text-alt text-gray-500">Value if items purchased separately</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiDollarSign className="w-4 h-4 opacity-70" />
                    <input
                      type="number"
                      name="regular_price"
                      value={formData.regular_price || 0}
                      onChange={handleInputChange}
                      className="grow"
                      min="0"
                      step="0.01"
                    />
                  </label>
                </div>
              </div>

              <div className="space-y-4">
                <div>
                  <label className="label">
                    <span className="label-text font-medium">Min Group Size</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiUsers className="w-4 h-4 opacity-70" />
                    <input
                      type="number"
                      name="min_group_size"
                      value={formData.min_group_size}
                      onChange={handleInputChange}
                      className="grow"
                      min="1"
                      required
                    />
                  </label>
                </div>

                <div>
                  <label className="label">
                    <span className="label-text font-medium">Max Group Size</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiUsers className="w-4 h-4 opacity-70" />
                    <input
                      type="number"
                      name="max_group_size"
                      value={formData.max_group_size}
                      onChange={handleInputChange}
                      className="grow"
                      min="1"
                      required
                    />
                  </label>
                </div>

                <div>
                  <label className="label">
                    <span className="label-text font-medium">For Who</span>
                  </label>
                  <label className="input input-bordered flex items-center gap-2 bg-white">
                    <FiUser className="w-4 h-4 opacity-70" />
                    <input
                      type="text"
                      name="for_who"
                      value={formData.for_who}
                      onChange={handleInputChange}
                      className="grow"
                    />
                  </label>
                </div>
              </div>
            </div>

            <div>
              <label className="label">
                <span className="label-text font-medium">Description</span>
              </label>
              <textarea
                name="description"
                value={formData.description}
                onChange={handleInputChange}
                className="textarea textarea-bordered w-full bg-white h-24"
                placeholder="Add a description for this deal..."
              />
            </div>
            
            <TagSelector 
              dealId={selectedDeal?.id || null}
              onTagsChange={handleTagsChange}
            />
            
            <div>
              <label className="label">
                <span className="label-text font-medium">Business Deal</span>
              </label>
              <div className={`p-3 rounded-lg border flex items-center gap-2 transition-colors duration-300 ${formData.is_for_business ? 'bg-blue-100 border-blue-300' : 'bg-gray-100 border-gray-300'}`}>
                <label className="cursor-pointer flex items-center gap-2">
                  <input
                    type="checkbox"
                    name="is_for_business"
                    checked={formData.is_for_business}
                    onChange={handleInputChange}
                    className="checkbox checkbox-primary w-4 h-4"
                  />
                  <span className="label-text font-medium flex items-center gap-2">
                    {formData.is_for_business ? (
                      <>
                        <FiCheckCircle className="text-blue-600" />
                        This is a business deal
                      </>
                    ) : (
                      <>
                        <FiToggleRight className="text-gray-600" />
                        Not a business deal
                      </>
                    )}
                  </span>
                </label>
              </div>
            </div>
            
            <div>
              <label className="label">
                <span className="label-text font-medium">Benefits</span>
                <span className="label-text text-gray-500 text-xs">Drag to reorder</span>
              </label>
              <div className="flex gap-2 mb-2">
                <label className="input input-bordered flex items-center gap-2 bg-white grow">
                  <FiCheckCircle className="w-4 h-4 opacity-70" />
                  <input
                    type="text"
                    value={newBenefit}
                    onChange={(e) => setNewBenefit(e.target.value)}
                    className="grow"
                    placeholder="Add a benefit"
                  />
                </label>
                <button
                  type="button"
                  onClick={handleAddBenefit}
                  className="btn btn-primary"
                >
                  Add
                </button>
              </div>
              
              {benefitsList.length > 0 && (
                <ul className="mt-2 space-y-1">
                  {benefitsList.map((benefit, index) => (
                    <BenefitItem 
                      key={index}
                      index={index}
                      benefit={benefit}
                      moveItem={moveBenefitItem}
                      onRemove={handleRemoveBenefit}
                      onEdit={handleEditBenefit}
                    />
                  ))}
                </ul>
              )}
            </div>
          </div>

          <div className="flex items-center justify-between p-6 border-t sticky bottom-0 bg-white">
            {selectedDeal?.id && (
              <button
                type="button"
                onClick={handleDelete}
                className="btn btn-error btn-sm"
              >
                Delete Deal
              </button>
            )}
            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => setIsDialogOpen(false)}
                className="btn btn-ghost btn-sm"
              >
                Cancel
              </button>
              <button
                type="submit"
                className="btn btn-primary btn-sm"
              >
                Save Changes
              </button>
            </div>
          </div>
        </form>
      </div>
      <div className="modal-backdrop bg-black/50" onClick={() => setIsDialogOpen(false)} />
    </dialog>
  );
}