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.
150 lines
5.2 KiB
Markdown
150 lines
5.2 KiB
Markdown
# 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:
|
|
|
|
```tsx
|
|
// 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:
|
|
|
|
```tsx
|
|
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 |