From 3f58f7fdbb39ba5a99f4f82e4a07b1b4aa664584 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 12 Dec 2025 11:25:03 +0100 Subject: [PATCH] drag and drop order of players --- package.json | 2 + .../match/components/SortablePlayerRow.tsx | 55 ++++++++ .../(main)/match/components/TeamSelection.tsx | 118 +++++++++++++----- yarn.lock | 46 +++++-- 4 files changed, 177 insertions(+), 44 deletions(-) create mode 100644 src/app/[locale]/(main)/match/components/SortablePlayerRow.tsx diff --git a/package.json b/package.json index 351fbb2..2097323 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ }, "dependencies": { "@auth/mongodb-adapter": "^3.5.2", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@headlessui-float/react": "^0.15.0", "@headlessui/react": "^2.1.8", "@headlessui/tailwindcss": "^0.2.1", diff --git a/src/app/[locale]/(main)/match/components/SortablePlayerRow.tsx b/src/app/[locale]/(main)/match/components/SortablePlayerRow.tsx new file mode 100644 index 0000000..e19c723 --- /dev/null +++ b/src/app/[locale]/(main)/match/components/SortablePlayerRow.tsx @@ -0,0 +1,55 @@ +import { CSSProperties, type ReactNode } from "react"; + +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; + +function SortablePlayerRow({ + field, + index, + renderField, +}: { + field: any; + index: number; + renderField: (field: any, index: number) => ReactNode; +}) { + const { + attributes, + listeners, + setNodeRef, + setActivatorNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: field.id }); + + const style: CSSProperties = { + transform: CSS.Transform.toString(transform), + transition, + marginBottom: "8px", + background: "#fff", + border: "1px solid #ccc", + padding: "8px", + opacity: isDragging ? 0.8 : 1, + }; + + return ( +
+
+ + +
{renderField(field, index)}
+
+
+ ); +} + +export default SortablePlayerRow; diff --git a/src/app/[locale]/(main)/match/components/TeamSelection.tsx b/src/app/[locale]/(main)/match/components/TeamSelection.tsx index c9724dd..5e62f28 100644 --- a/src/app/[locale]/(main)/match/components/TeamSelection.tsx +++ b/src/app/[locale]/(main)/match/components/TeamSelection.tsx @@ -2,6 +2,17 @@ import { useEffect, useState } from "react"; +import { + DndContext, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + SortableContext, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; import { useAtom } from "jotai"; import { Controller, @@ -15,6 +26,8 @@ import Select from "react-select"; import { clubPlayersAtom } from "@/atoms"; import { fetchClubPlayers } from "@/server/fetchClubPlayers"; +import SortablePlayerRow from "./SortablePlayerRow"; + interface IProps { name: string; label: string; @@ -32,11 +45,17 @@ const TeamSelection = ({ name, label, clubOptions, className }: IProps) => { const { control } = useFormContext(); const [teamsPlayers, setTeamsPlayers] = useAtom(clubPlayersAtom); - const { fields, append, remove } = useFieldArray({ + const { fields, append, remove, move } = useFieldArray({ control, name: `${name}_players`, // e.g., "team1_players" }); + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { distance: 6 }, + }), + ); + const selectedDbId = useWatch({ control, name: name, @@ -118,42 +137,73 @@ const TeamSelection = ({ name, label, clubOptions, className }: IProps) => { )} /> -
- {fields.map((field, index) => ( -
-
- ( - + selectField.onChange(option?.value) + } + value={playerOptions.find( + (op) => op.value === selectField.value, + )} + classNames={{ + control: () => + "border-gray-300 rounded-md shadow-sm", + }} + /> + )} + /> +
+ + +
)} /> -
- + ))} - ))} - + + {isLoading ? (

Loading players...

diff --git a/yarn.lock b/yarn.lock index ae0be01..376dda5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,11 +7,6 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== -"@ag-media/react-pdf-table@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@ag-media/react-pdf-table/-/react-pdf-table-2.0.3.tgz#113554b583b46e41a098cf64fecb5decd59ba004" - integrity sha512-IscjfAOKwsyQok9YmzvuToe6GojN7J8hF0kb8C+K8qZX1DvhheGO+hRSAPxbv2nKMbSpvk7CIhSqJEkw++XVWg== - "@alloc/quick-lru@^5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" @@ -1127,6 +1122,37 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@dnd-kit/accessibility@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" + integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.3.1.tgz#4c36406a62c7baac499726f899935f93f0e6d003" + integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ== + dependencies: + "@dnd-kit/accessibility" "^3.1.1" + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/sortable@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-10.0.0.tgz#1f9382b90d835cd5c65d92824fa9dafb78c4c3e8" + integrity sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg== + dependencies: + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b" + integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== + dependencies: + tslib "^2.0.0" + "@emnapi/runtime@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.2.0.tgz#71d018546c3a91f3b51106530edbc056b9f2f2e3" @@ -6704,16 +6730,16 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@^2.0.0, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^2.4.0, tslib@^2.6.2: version "2.7.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== -tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"