"use client"; import { useState, useRef, useEffect } from "react"; import { createPortal } from "react-dom"; import Badge from "@/components/ui/Badge"; import { useTranslation } from "@/lib/i18n"; export default function TaskStatusDropdown({ task, size = "sm", showDropdown = true, onStatusChange, }) { const { t } = useTranslation(); const [status, setStatus] = useState(task.status); const [loading, setLoading] = useState(false); const [isOpen, setIsOpen] = useState(false); const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0, position: 'bottom' }); const [mounted, setMounted] = useState(false); const buttonRef = useRef(null); const dropdownRef = useRef(null); useEffect(() => { setMounted(true); }, []); const statusConfig = { not_started: { label: t("taskStatus.not_started"), variant: "secondary", }, pending: { label: t("taskStatus.pending"), variant: "warning", }, in_progress: { label: t("taskStatus.in_progress"), variant: "primary", }, completed: { label: t("taskStatus.completed"), variant: "success", }, cancelled: { label: t("taskStatus.cancelled"), variant: "danger", }, }; const handleChange = async (newStatus) => { if (newStatus === status) { setIsOpen(false); return; } setStatus(newStatus); setLoading(true); setIsOpen(false); try { const res = await fetch(`/api/project-tasks/${task.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: newStatus }), }); if (res.ok) { // Call the callback if provided (for parent component to refresh) if (onStatusChange) { onStatusChange(task.id, newStatus); } } else { // Revert on error setStatus(task.status); alert("Failed to update task status"); } } catch (error) { console.error("Failed to update status:", error); setStatus(task.status); // Revert on error alert("Error updating task status"); } finally { setLoading(false); } }; // Calculate dropdown position to avoid clipping const calculateDropdownPosition = () => { if (!buttonRef.current) return; const buttonRect = buttonRef.current.getBoundingClientRect(); const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth; const dropdownHeight = 180; // Estimated dropdown height const dropdownWidth = 140; // Estimated dropdown width // Check if there's enough space below const spaceBelow = viewportHeight - buttonRect.bottom; const spaceAbove = buttonRect.top; // Determine vertical position let y, position; if (spaceBelow < dropdownHeight && spaceAbove > dropdownHeight) { // Show above y = buttonRect.top - dropdownHeight - 4; position = 'top'; } else { // Show below y = buttonRect.bottom + 4; position = 'bottom'; } // Determine horizontal position (align right edge of dropdown with right edge of button) let x = buttonRect.right - dropdownWidth; // Ensure dropdown doesn't go off screen if (x < 8) { x = 8; // Minimum margin from left edge } else if (x + dropdownWidth > viewportWidth - 8) { x = viewportWidth - dropdownWidth - 8; // Ensure it fits within viewport } setDropdownPosition({ x, y, position }); }; const handleToggle = () => { if (!isOpen) { calculateDropdownPosition(); } setIsOpen(!isOpen); }; // Close dropdown when clicking outside useEffect(() => { if (!isOpen) return; const handleClickOutside = (event) => { if ( buttonRef.current && !buttonRef.current.contains(event.target) && dropdownRef.current && !dropdownRef.current.contains(event.target) ) { setIsOpen(false); } }; const handleScroll = () => { if (isOpen) { calculateDropdownPosition(); } }; document.addEventListener('mousedown', handleClickOutside); window.addEventListener('scroll', handleScroll); window.addEventListener('resize', handleScroll); return () => { document.removeEventListener('mousedown', handleClickOutside); window.removeEventListener('scroll', handleScroll); window.removeEventListener('resize', handleScroll); }; }, [isOpen]); const currentConfig = statusConfig[status] || { label: "Unknown", variant: "default", }; if (!showDropdown) { return ( {currentConfig.label} ); } // Portal dropdown component const DropdownPortal = () => { if (!mounted || !isOpen) return null; return createPortal(
{Object.entries(statusConfig).map(([statusKey, config]) => ( ))}
, document.body ); }; return ( <> {/* Portal-based dropdown */} ); }