'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>
);
}