feat(GRO-2159): drag-to-reorder + re-optimize on route planner
CI / Test (pull_request) Successful in 24s
CI / Lint & Typecheck (pull_request) Successful in 31s
CI / Build & Push Docker Image (pull_request) Successful in 50s

Add @dnd-kit drag-and-drop reorder to the /admin/routes stop list.

- Install @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Sortable stop cards with a grab handle; pointer + touch (press-and-hold)
  + keyboard sensors so mobile groomers and a11y users can reorder
- On drop, PATCH /api/routes/:routeId/reorder { stopOrder } (full order)
- Optimistic UI update with rollback + error message on failure
- Server recomputes legs/buffers/conflicts; response adopted authoritatively
- Tight-schedule conflict warnings retained (server-flagged, gap < travel+buffer)
- "Re-optimize" hint+button after a manual reorder (re-runs POST /optimize)
- Tests: drag handles, conflict flag, no pre-reorder hint, reorder mock
- Updated UAT_PLAYBOOK.md §5.29 — drag-to-reorder & re-optimize cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-06-09 02:55:39 +00:00
parent c58e4e4b23
commit 2e5e35dcea
5 changed files with 330 additions and 48 deletions
+61
View File
@@ -8,6 +8,15 @@ importers:
.:
dependencies:
'@dnd-kit/core':
specifier: ^6.3.1
version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@dnd-kit/sortable':
specifier: ^10.0.0
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@19.2.6)
'@groombook/types':
specifier: workspace:*
version: link:packages/types
@@ -748,6 +757,28 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
'@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies:
react: '>=16.8.0'
'@dnd-kit/core@6.3.1':
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@dnd-kit/sortable@10.0.0':
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
peerDependencies:
'@dnd-kit/core': ^6.3.0
react: '>=16.8.0'
'@dnd-kit/utilities@3.2.2':
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
peerDependencies:
react: '>=16.8.0'
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
@@ -3176,6 +3207,9 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -4261,6 +4295,31 @@ snapshots:
'@csstools/css-tokenizer@3.0.4': {}
'@dnd-kit/accessibility@3.1.1(react@19.2.6)':
dependencies:
react: 19.2.6
tslib: 2.8.1
'@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@dnd-kit/accessibility': 3.1.1(react@19.2.6)
'@dnd-kit/utilities': 3.2.2(react@19.2.6)
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
tslib: 2.8.1
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)':
dependencies:
'@dnd-kit/core': 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@dnd-kit/utilities': 3.2.2(react@19.2.6)
react: 19.2.6
tslib: 2.8.1
'@dnd-kit/utilities@3.2.2(react@19.2.6)':
dependencies:
react: 19.2.6
tslib: 2.8.1
'@esbuild/aix-ppc64@0.25.12':
optional: true
@@ -6627,6 +6686,8 @@ snapshots:
dependencies:
typescript: 5.9.3
tslib@2.8.1: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1