|
1 |
| -import { useSelectedNode } from "../../store/configSlice/configSliceHooks"; |
| 1 | +import { useCallback, useEffect, useRef, useState } from "react"; |
| 2 | +import { useAppDispatch } from "../../store"; |
| 3 | +import { useConfigNodes, useSelectedNode } from "../../store/configSlice/configSliceHooks"; |
| 4 | +import { selectNode } from "../../store/configSlice/configSlice"; |
| 5 | +import { useAddMiner } from "../add-miner/use-add-miner"; |
| 6 | +import { AddMinerModal } from "../add-miner/add-miner-modal"; |
2 | 7 |
|
3 | 8 | export function SelectMinerDropdown() {
|
| 9 | + const dropdownRef = useRef<HTMLDivElement>(null); |
| 10 | + const dispatch = useAppDispatch(); |
| 11 | + const [isDropdownOpen, setIsDropdownOpen] = useState(false); |
4 | 12 | const selectedNode = useSelectedNode();
|
| 13 | + const configuredNodes = useConfigNodes(); |
| 14 | + const { |
| 15 | + isModalOpen, |
| 16 | + newMinerName, |
| 17 | + newMinerHost, |
| 18 | + newMinerPort, |
| 19 | + newMinerProtocol, |
| 20 | + handleAddMiner, |
| 21 | + handleNewMinerNameChange, |
| 22 | + handleNewMinerHostChange, |
| 23 | + handleNewMinerPortChange, |
| 24 | + handleNewMinerProtocolChange, |
| 25 | + setIsModalOpen, |
| 26 | + } = useAddMiner(); |
| 27 | + |
| 28 | + const handleSelectNode = useCallback( |
| 29 | + (nodeId: string) => { |
| 30 | + if (selectedNode?.id !== nodeId) { |
| 31 | + dispatch(selectNode(nodeId)); |
| 32 | + } |
| 33 | + setIsDropdownOpen(false); |
| 34 | + }, |
| 35 | + [dispatch], |
| 36 | + ); |
| 37 | + |
| 38 | + const handleOutsideClick = useCallback( |
| 39 | + (event: MouseEvent) => { |
| 40 | + if ( |
| 41 | + dropdownRef.current && |
| 42 | + event.target && |
| 43 | + !dropdownRef.current.contains(event.target as Node) |
| 44 | + ) { |
| 45 | + setIsDropdownOpen(false); |
| 46 | + } |
| 47 | + }, |
| 48 | + [setIsDropdownOpen], |
| 49 | + ); |
| 50 | + |
| 51 | + useEffect(() => { |
| 52 | + if (isDropdownOpen) { |
| 53 | + document.addEventListener("mousedown", handleOutsideClick); |
| 54 | + } else { |
| 55 | + document.removeEventListener("mousedown", handleOutsideClick); |
| 56 | + } |
| 57 | + |
| 58 | + // Cleanup the event listener on unmount |
| 59 | + return () => { |
| 60 | + document.removeEventListener("mousedown", handleOutsideClick); |
| 61 | + }; |
| 62 | + }, [isDropdownOpen]); |
5 | 63 |
|
6 | 64 | return (
|
7 |
| - <button |
8 |
| - className="flex items-center gap-2 border border-gray-950 rounded-md px-4 py-2 font-normal outline-none text-gray-950 hover:bg-gray-200" |
9 |
| - type="button" |
10 |
| - > |
11 |
| - <span>{selectedNode?.name || "loading..."}</span> |
12 |
| - </button> |
| 65 | + <div className="relative" ref={dropdownRef}> |
| 66 | + <button |
| 67 | + className="flex items-center gap-2 border border-gray-950 rounded-md px-4 py-2 font-normal outline-none text-gray-950 hover:bg-gray-200" |
| 68 | + type="button" |
| 69 | + onClick={() => setIsDropdownOpen(!isDropdownOpen)} |
| 70 | + > |
| 71 | + <span>{selectedNode?.name || "loading..."}</span> |
| 72 | + </button> |
| 73 | + {isDropdownOpen && ( |
| 74 | + <div className="absolute z-10 top-0 right-0 mt-12 w-56 rounded-md shadow-lg bg-gray-950 ring-1 ring-black ring-opacity-5"> |
| 75 | + <div |
| 76 | + className="py-1" |
| 77 | + role="menu" |
| 78 | + aria-orientation="vertical" |
| 79 | + aria-labelledby="options-menu" |
| 80 | + > |
| 81 | + {configuredNodes.map((node) => ( |
| 82 | + <a |
| 83 | + key={node.id} |
| 84 | + href="#" |
| 85 | + className="block px-2 py-2 text-sm text-gray-200 hover:bg-gray-700 hover:text-white" |
| 86 | + role="menuitem" |
| 87 | + onClick={(event) => { |
| 88 | + if (event.target instanceof HTMLButtonElement) { |
| 89 | + event.preventDefault(); |
| 90 | + } else { |
| 91 | + handleSelectNode(node.id); |
| 92 | + } |
| 93 | + }} |
| 94 | + > |
| 95 | + {node.id === selectedNode?.id ? <span>✔</span> : <span className="w-2">·</span>}{" "} |
| 96 | + {node.name}{" "} |
| 97 | + <button |
| 98 | + className="rounded-full p-0 px-2 hover:bg-gray-500 border-gray-200 border-solid border-2" |
| 99 | + onClick={() => window.alert("TODO: edit miner")} |
| 100 | + > |
| 101 | + ✎ |
| 102 | + </button> |
| 103 | + <button |
| 104 | + className="rounded-full p-0 px-2 ml-2 hover:bg-gray-500 border-gray-200 border-solid border-2" |
| 105 | + onClick={() => window.alert("TODO: delete miner")} |
| 106 | + > |
| 107 | + 🗑 |
| 108 | + </button> |
| 109 | + </a> |
| 110 | + ))} |
| 111 | + <hr /> |
| 112 | + <a |
| 113 | + href="#" |
| 114 | + className="block px-4 py-2 text-sm text-gray-200 hover:bg-gray-700 hover:text-white" |
| 115 | + role="menuitem" |
| 116 | + onClick={() => { |
| 117 | + setIsDropdownOpen(false); |
| 118 | + setIsModalOpen(true); |
| 119 | + }} |
| 120 | + > |
| 121 | + <span>+</span> Add new miner |
| 122 | + </a> |
| 123 | + </div> |
| 124 | + </div> |
| 125 | + )} |
| 126 | + {isModalOpen && ( |
| 127 | + <AddMinerModal |
| 128 | + onClose={() => setIsModalOpen(false)} |
| 129 | + onAddMiner={handleAddMiner} |
| 130 | + nameValue={newMinerName} |
| 131 | + hostnameValue={newMinerHost} |
| 132 | + portValue={newMinerPort} |
| 133 | + protocolValue={newMinerProtocol} |
| 134 | + onNameChange={handleNewMinerNameChange} |
| 135 | + onHostnameChange={handleNewMinerHostChange} |
| 136 | + onPortChange={handleNewMinerPortChange} |
| 137 | + onProtocolChange={handleNewMinerProtocolChange} |
| 138 | + /> |
| 139 | + )} |
| 140 | + </div> |
13 | 141 | );
|
14 | 142 | }
|
0 commit comments