import DeleteIcon from '@mui/icons-material/Delete' import EditIcon from '@mui/icons-material/Edit' import { Avatar, Box, Chip, CircularProgress, Container, IconButton, Typography, } from '@mui/joy' import { useEffect, useRef, useState } from 'react' import LabelModal from '../Modals/Inputs/LabelModal' // import { useMutation, useQueryClient } from '@tanstack/react-query' import { Add, ColorLens } from '@mui/icons-material' import { useQueryClient } from '@tanstack/react-query' import { useUserProfile } from '../../queries/UserQueries' import LABEL_COLORS, { getTextColorFromBackgroundColor, } from '../../utils/Colors' import { DeleteLabel } from '../../utils/Fetcher' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import { useLabels } from './LabelQueries' const LabelCard = ({ label, onEditClick, onDeleteClick, currentUserId }) => { // Helper function to get color name from hex value const getColorName = hexValue => { const colorObj = LABEL_COLORS.find( color => color.value.toLowerCase() === hexValue.toLowerCase(), ) return colorObj ? colorObj.name : hexValue } // Check if current user owns this label const isOwnedByCurrentUser = label.created_by === currentUserId // Swipe functionality state const [swipeTranslateX, setSwipeTranslateX] = useState(0) const [isDragging, setIsDragging] = useState(false) const [isSwipeRevealed, setIsSwipeRevealed] = useState(false) const [hoverTimer, setHoverTimer] = useState(null) const swipeThreshold = 80 const maxSwipeDistance = 160 const dragStartX = useRef(0) const cardRef = useRef(null) // Swipe gesture handlers const handleTouchStart = e => { dragStartX.current = e.touches[0].clientX setIsDragging(true) } const handleTouchMove = e => { if (!isDragging) return const currentX = e.touches[0].clientX const deltaX = currentX - dragStartX.current if (isSwipeRevealed) { if (deltaX > 0) { const clampedDelta = Math.min(deltaX - maxSwipeDistance, 0) setSwipeTranslateX(clampedDelta) } } else { if (deltaX < 0) { const clampedDelta = Math.max(deltaX, -maxSwipeDistance) setSwipeTranslateX(clampedDelta) } } } const handleTouchEnd = () => { if (!isDragging) return setIsDragging(false) if (isSwipeRevealed) { if (swipeTranslateX > -swipeThreshold) { setSwipeTranslateX(0) setIsSwipeRevealed(false) } else { setSwipeTranslateX(-maxSwipeDistance) } } else { if (Math.abs(swipeTranslateX) > swipeThreshold) { setSwipeTranslateX(-maxSwipeDistance) setIsSwipeRevealed(true) } else { setSwipeTranslateX(0) setIsSwipeRevealed(false) } } } const handleMouseDown = e => { dragStartX.current = e.clientX setIsDragging(true) } const handleMouseMove = e => { if (!isDragging) return const currentX = e.clientX const deltaX = currentX - dragStartX.current if (isSwipeRevealed) { if (deltaX > 0) { const clampedDelta = Math.min(deltaX - maxSwipeDistance, 0) setSwipeTranslateX(clampedDelta) } } else { if (deltaX < 0) { const clampedDelta = Math.max(deltaX, -maxSwipeDistance) setSwipeTranslateX(clampedDelta) } } } const handleMouseUp = () => { if (!isDragging) return setIsDragging(false) if (isSwipeRevealed) { if (swipeTranslateX > -swipeThreshold) { setSwipeTranslateX(0) setIsSwipeRevealed(false) } else { setSwipeTranslateX(-maxSwipeDistance) } } else { if (Math.abs(swipeTranslateX) > swipeThreshold) { setSwipeTranslateX(-maxSwipeDistance) setIsSwipeRevealed(true) } else { setSwipeTranslateX(0) setIsSwipeRevealed(false) } } } const resetSwipe = () => { setSwipeTranslateX(0) setIsSwipeRevealed(false) } // Hover functionality for desktop - only trigger from drag area const handleMouseEnter = () => { if (isSwipeRevealed) return const timer = setTimeout(() => { setSwipeTranslateX(-maxSwipeDistance) setIsSwipeRevealed(true) setHoverTimer(null) }, 800) // Shorter delay for drag area setHoverTimer(timer) } const handleMouseLeave = () => { if (hoverTimer) { clearTimeout(hoverTimer) setHoverTimer(null) } // Only add hide timer if we're leaving the drag area and actions are NOT revealed // If actions are revealed, let the action area handle the hiding if (!isSwipeRevealed) { // Actions are not revealed, so we can safely hide after delay const hideTimer = setTimeout(() => { resetSwipe() }, 300) setHoverTimer(hideTimer) } } const handleActionAreaMouseEnter = () => { // Clear any pending timer when entering action area if (hoverTimer) { clearTimeout(hoverTimer) setHoverTimer(null) } } const handleActionAreaMouseLeave = () => { // Hide immediately when leaving action area if (isSwipeRevealed) { resetSwipe() } } // Clean up timer on unmount useEffect(() => { return () => { if (hoverTimer) { clearTimeout(hoverTimer) } } }, [hoverTimer]) return ( { // Only clear timers, don't auto-hide if (hoverTimer) { clearTimeout(hoverTimer) setHoverTimer(null) } }} > {/* Action buttons underneath (revealed on swipe) */} { e.stopPropagation() resetSwipe() onEditClick(label) }} sx={{ width: 40, height: 40, mx: 1, bgcolor: 'primary.100', color: 'primary.600', '&:hover': { bgcolor: 'primary.200', }, }} > { e.stopPropagation() resetSwipe() onDeleteClick(label.id) }} sx={{ width: 40, height: 40, mx: 1, bgcolor: 'danger.100', color: 'danger.600', '&:hover': { bgcolor: 'danger.200', }, }} > {/* Main card content */} { if (isSwipeRevealed) { resetSwipe() return } // Optional: Navigate to label details or edit directly onEditClick(label) }} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > {/* Right drag area - only triggers reveal on hover */} {/* Drag indicator dots */} {[...Array(3)].map((_, i) => ( ))} {/* Color Avatar */} {label.name.charAt(0).toUpperCase()} {/* Content - Center */} {/* Label Name */} {label.name} {/* Color Info */} {label.color && ( } sx={{ fontSize: 10, height: 18, px: 0.75, bgcolor: `${label.color}20`, color: label.color, border: `1px solid ${label.color}30`, }} > {getColorName(label.color)} )} {!isOwnedByCurrentUser && ( Shared )} ) } const LabelView = () => { const { data: labels, isLabelsLoading, isError } = useLabels() const { data: userProfile } = useUserProfile() const [userLabels, setUserLabels] = useState([]) const [modalOpen, setModalOpen] = useState(false) const [currentLabel, setCurrentLabel] = useState(null) const queryClient = useQueryClient() const [confirmationModel, setConfirmationModel] = useState({}) const handleAddLabel = () => { setCurrentLabel(null) setModalOpen(true) } const handleEditLabel = label => { setCurrentLabel(label) setModalOpen(true) } const handleDeleteClicked = id => { setConfirmationModel({ isOpen: true, title: 'Delete Label', message: 'Are you sure you want to delete this label? This will remove the label from all tasks.', confirmText: 'Delete', color: 'danger', cancelText: 'Cancel', onClose: confirmed => { if (confirmed === true) { handleDeleteLabel(id) } setConfirmationModel({}) }, }) } const handleDeleteLabel = id => { DeleteLabel(id).then(() => { const updatedLabels = userLabels.filter(label => label.id !== id) setUserLabels(updatedLabels) queryClient.invalidateQueries('labels') }) } const handleSaveLabel = newOrUpdatedLabel => { queryClient.invalidateQueries('labels') setModalOpen(false) const updatedLabels = userLabels.map(label => label.id === newOrUpdatedLabel.id ? newOrUpdatedLabel : label, ) setUserLabels(updatedLabels) } useEffect(() => { if (labels) { setUserLabels(labels) } }, [labels]) if (isLabelsLoading) { return ( ) } if (isError) { return ( Failed to load labels. Please try again. ) } return ( {userLabels.length === 0 && ( No labels available. Add a new label to get started. )} {userLabels.map(label => ( ))} {modalOpen && ( setModalOpen(false)} onSave={handleSaveLabel} label={currentLabel} /> )} ) } export default LabelView