You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.2 KiB

DraggableCourt Integration Strategy for Booking Slot Page

Overview

This document outlines the strategy for integrating the new DraggableCourt component into the existing booking slot page, replacing the current CourtWithPlayers component.

Position Mapping Challenge

The key challenge is the difference in how positions are handled:

Current System (CourtWithPlayers)

  • Uses logical positions (0-3) stored in currentPosition
  • Visual arrangement uses displayToLogical = [0, 1, 3, 2]
  • This means:
    • Logical position 0 → Visual position 0 (top-left)
    • Logical position 1 → Visual position 1 (bottom-left)
    • Logical position 2 → Visual position 3 (bottom-right)
    • Logical position 3 → Visual position 2 (top-right)

New System (DraggableCourt)

  • Uses direct visual positions (0-3) in the position field
  • Visual arrangement is straightforward: [0, 1, 2, 3]
  • This means:
    • Position 0 → top-left
    • Position 1 → bottom-left
    • Position 2 → top-right
    • Position 3 → bottom-right

Integration Steps

1. Add Transformation Utilities

Created src/utils/draggableCourtAdapter.ts with:

  • transformPlayersToDraggableCourt(): Converts players from logical to visual positions
  • transformSwapToLogical(): Converts visual swap to logical positions
  • updatePlayersAfterDraggableSwap(): Updates player array after a swap

2. Update Booking Slot Page

Replace the CourtWithPlayers component with DraggableCourtWrapper:

// In src/app/[locale]/booking/slot/[slot_id]/page.tsx

import DraggableCourtWrapper from '@/src/components/DraggableCourtWrapper';
import { transformPlayersToDraggableCourt, transformSwapToLogical } from '@/src/utils/draggableCourtAdapter';

// Inside component:
const draggableCourtPlayers = useMemo(
  () => transformPlayersToDraggableCourt(orderedPlayers),
  [orderedPlayers]
);

const handleDraggableSwap = useCallback((fromDisplay: number, toDisplay: number) => {
  // Convert to logical positions for recording
  const [fromLogical, toLogical] = transformSwapToLogical(fromDisplay, toDisplay);
  
  // Record the swap in logical positions
  setRecordedSwaps(prev => [...prev, [fromLogical, toLogical]]);
  
  // Update players using existing logic
  setPlayersWithCurrentOrder(prev => {
    const fromPlayerIndex = prev.findIndex(p => p.currentPosition === fromLogical);
    const toPlayerIndex = prev.findIndex(p => p.currentPosition === toLogical);
    
    if (fromPlayerIndex === -1) return prev;
    
    const copy = [...prev];
    
    if (toPlayerIndex !== -1) {
      // Swap players
      [copy[fromPlayerIndex], copy[toPlayerIndex]] = [
        { ...copy[toPlayerIndex], currentPosition: fromLogical },
        { ...copy[fromPlayerIndex], currentPosition: toLogical }
      ];
    } else {
      // Move to empty position
      copy[fromPlayerIndex] = {
        ...copy[fromPlayerIndex],
        currentPosition: toLogical
      };
    }
    
    return copy;
  });
}, [setRecordedSwaps, setPlayersWithCurrentOrder]);

// Replace CourtWithPlayers with:
<DraggableCourtWrapper
  players={draggableCourtPlayers}
  onSwap={handleDraggableSwap}
  courtColor={getCourtColor(orderedPlayers, booking.status || SlotStatus.available)}
  isDraggable={!disableDrag}
  className="h-64"
/>

3. Handle Empty Slots

For the "add player" functionality, we need to handle empty positions:

const handleEmptyPositionClick = (displayPosition: number) => {
  const logicalPosition = displayToLogical[displayPosition];
  handleEmptySlotClick(logicalPosition);
};

4. Anonymous Players for Past Bookings

The DraggableCourt already handles missing players by showing empty positions, so we don't need to create anonymous players like in CourtWithPlayers.

5. Props Mapping

CourtWithPlayers DraggableCourtWrapper
players players (transformed)
onOrderChange + onSwapRecorded onSwap
fixed isDraggable (inverted)
className className
slotStatus Used for courtColor
showAddButtons Not needed (always shows empty slots)
onEmptySlotClick Not directly supported yet
pendingJoinPositions Not supported yet
isPastBooking Not needed

Benefits of Migration

  1. Better Mobile Support: @dnd-kit provides superior touch handling
  2. Smoother Animations: Built-in drag overlay and transitions
  3. Accessibility: Better keyboard and screen reader support
  4. Smaller Code: Less custom drag logic to maintain
  5. Customizable Collision Detection: Already implemented bottom-right collision point

Testing Checklist

  • Visual positions match exactly between old and new components
  • Swaps are recorded with correct logical positions
  • Position updates persist correctly
  • Mobile drag and drop works smoothly
  • Keyboard navigation works (if enabled)
  • Empty positions display correctly
  • Court colors match based on player count
  • Disabled state works when disableDrag is true

Future Enhancements

  1. Add support for onEmptySlotClick to handle "add player" functionality
  2. Add visual indicators for pendingJoinPositions
  3. Consider adding animation for position changes
  4. Add haptic feedback on mobile devices