@@ -11,25 +11,86 @@ import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
1111import RefreshIcon from "@material-ui/icons/Refresh" ;
1212import { useCallback , useEffect , useState } from "react" ;
1313import DataTable from "react-data-table-component" ;
14+ import { DndProvider , useDrag , useDrop } from "react-dnd" ;
15+ import { HTML5Backend } from "react-dnd-html5-backend" ;
1416import { useParams } from "react-router-dom" ;
1517import API from "utils/API" ;
1618import { parseValue , replaceValue , setValue } from "utils/ChangeHelper" ;
1719import { formatDistance } from "date-fns" ;
1820import AddMember from "./components/AddMember" ;
21+ import AddGroup from "./components/AddGroup" ;
1922import DeleteMember from "./components/DeleteMember" ;
2023import ManagedIP from "./components/ManagedIP" ;
2124import MemberName from "./components/MemberName" ;
2225import MemberSettings from "./components/MemberSettings" ;
2326
2427import { useTranslation } from "react-i18next" ;
2528
29+ const MemberItemType = "MEMBER_ITEM" ;
30+
31+ const DraggableRow = ( { zoneId, row, content, ...other } ) => {
32+ const [ , drag ] = useDrag ( {
33+ type : MemberItemType ,
34+ item : { zoneId, row } ,
35+ } ) ;
36+
37+ return (
38+ < div style = { { userSelect : "text" } } ref = { ( node ) => drag ( node ) } { ...other } >
39+ { content }
40+ </ div >
41+ ) ;
42+ } ;
43+
44+ const DropZone = ( { zoneId, moveRow, children } ) => {
45+ const [ , drop ] = useDrop ( {
46+ accept : MemberItemType ,
47+ canDrop : ( item , monitor ) => {
48+ return item . zoneId !== zoneId ;
49+ } ,
50+ drop : ( item , monitor ) => {
51+ if ( monitor . canDrop ( ) ) {
52+ moveRow ( item . zoneId , item . row , zoneId ) ;
53+ }
54+ } ,
55+ collect : ( monitor ) => ( {
56+ isOver : monitor . isOver ( ) ,
57+ canDrop : monitor . canDrop ( ) ,
58+ } ) ,
59+ } ) ;
60+
61+ return (
62+ < div
63+ style = { { all : "unset" , display : "contents" } }
64+ ref = { ( node ) => drop ( node ) }
65+ >
66+ { children }
67+ </ div >
68+ ) ;
69+ } ;
70+
2671function NetworkMembers ( { network } ) {
2772 const { nwid } = useParams ( ) ;
2873 const [ members , setMembers ] = useState ( [ ] ) ;
74+ const [ groups , setGroups ] = useState ( [ ] ) ;
75+ const [ extraGroups , setExtraGroups ] = useState ( [ ] ) ;
76+
77+ const addGroup = useCallback (
78+ async ( name ) => {
79+ if ( ! groups . includes ( name ) && ! groups . includes ( name ) ) {
80+ let mutableExtraGroups = [ ...extraGroups ] ;
81+ mutableExtraGroups . push ( name ) ;
82+ setExtraGroups ( mutableExtraGroups ) ;
83+ }
84+ } ,
85+ [ groups , extraGroups ]
86+ ) ;
2987
3088 const fetchData = useCallback ( async ( ) => {
3189 try {
3290 const members = await API . get ( "network/" + nwid + "/member" ) ;
91+ let groupSet = new Set ( ) ;
92+ members . data . forEach ( ( x ) => groupSet . add ( x . group ) ) ;
93+ setGroups ( [ ...groupSet ] ) ;
3394 setMembers ( members . data ) ;
3495 console . log ( "Members:" , members . data ) ;
3596 } catch ( err ) {
@@ -62,7 +123,13 @@ function NetworkMembers({ network }) {
62123 } ) ;
63124 let mutableMembers = [ ...members ] ;
64125 mutableMembers [ index ] = updatedMember ;
126+ const groups = new Set ( ) ;
127+ mutableMembers . forEach ( ( x ) => groups . add ( x . group ) ) ;
128+ let mutableExtraGroups = extraGroups . filter ( ( x ) => ! groups . has ( x ) ) ;
129+
65130 setMembers ( mutableMembers ) ;
131+ setGroups ( [ ...groups ] ) ;
132+ setExtraGroups ( mutableExtraGroups ) ;
66133
67134 const data = setValue ( { } , key1 , key2 , value ) ;
68135 sendReq ( member [ "config" ] [ "id" ] , data ) ;
@@ -163,44 +230,95 @@ function NetworkMembers({ network }) {
163230 } ,
164231 ] ;
165232
233+ const changeMemberGroup = ( oldGroup , member , newGroup ) => {
234+ member . group = newGroup ;
235+
236+ let mutableMembers = [ ...members ] ;
237+ const groups = new Set ( ) ;
238+ mutableMembers . forEach ( ( x ) => groups . add ( x . group ) ) ;
239+
240+ // Remove extra group
241+ let mutableExtraGroups = extraGroups . filter ( ( x ) => ! groups . has ( x ) ) ;
242+
243+ setGroups ( [ ...groups ] ) ;
244+ setExtraGroups ( mutableExtraGroups ) ;
245+ setMembers ( mutableMembers ) ;
246+
247+ const data = setValue ( { } , "group" , null , newGroup ) ;
248+ sendReq ( member [ "config" ] [ "id" ] , data ) ;
249+ } ;
250+
166251 return (
167252 < Accordion defaultExpanded = { true } >
168253 < AccordionSummary expandIcon = { < ExpandMoreIcon /> } >
169254 < Typography > { t ( "member" , { count : members . length } ) } </ Typography >
170255 </ AccordionSummary >
171256 < AccordionDetails >
172- < Grid container direction = "column" spacing = { 3 } >
173- < IconButton color = "primary" onClick = { fetchData } >
174- < RefreshIcon />
175- </ IconButton >
176- < Grid container >
177- { members . length ? (
178- < DataTable
179- noHeader = { true }
180- columns = { columns }
181- data = { [ ...members ] }
182- />
183- ) : (
184- < Grid
185- container
186- spacing = { 0 }
187- direction = "column"
188- alignItems = "center"
189- justifyContent = "center"
190- style = { {
191- minHeight : "50vh" ,
192- } }
193- >
194- < Typography variant = "h6" style = { { padding : "10%" } } >
195- { t ( "noDevices" ) } < b > { nwid } </ b > .
196- </ Typography >
197- </ Grid >
198- ) }
199- </ Grid >
200- < Grid item >
201- < AddMember nwid = { nwid } callback = { fetchData } />
257+ < DndProvider backend = { HTML5Backend } >
258+ < Grid container direction = "column" spacing = { 3 } >
259+ { groups
260+ . concat ( extraGroups )
261+ . sort ( )
262+ . map ( ( group ) => (
263+ < Accordion defaultExpanded = { group == "" } key = { group } >
264+ < AccordionSummary expandIcon = { < ExpandMoreIcon /> } >
265+ < Typography > { group || "Ungrouped" } </ Typography >
266+ </ AccordionSummary >
267+ < AccordionDetails >
268+ < Grid container direction = "column" spacing = { 3 } >
269+ < IconButton color = "primary" onClick = { fetchData } >
270+ < RefreshIcon />
271+ </ IconButton >
272+ < Grid container >
273+ < DropZone zoneId = { group } moveRow = { changeMemberGroup } >
274+ { members . length ? (
275+ < DataTable
276+ noHeader = { true }
277+ columns = { columns }
278+ data = { [
279+ ...members . filter ( ( x ) => x . group == group ) ,
280+ ] }
281+ renderRow = { ( row , content ) => (
282+ < DraggableRow
283+ zoneId = { group }
284+ row = { row }
285+ content = { content }
286+ key = { row . config . address }
287+ />
288+ ) }
289+ />
290+ ) : (
291+ < Grid
292+ container
293+ spacing = { 0 }
294+ direction = "column"
295+ alignItems = "center"
296+ justifyContent = "center"
297+ style = { {
298+ minHeight : "50vh" ,
299+ } }
300+ >
301+ < Typography
302+ variant = "h6"
303+ style = { { padding : "10%" } }
304+ >
305+ { t ( "noDevices" ) } < b > { nwid } </ b > .
306+ </ Typography >
307+ </ Grid >
308+ ) }
309+ </ DropZone >
310+ </ Grid >
311+ < Grid item > </ Grid >
312+ </ Grid >
313+ </ AccordionDetails >
314+ </ Accordion >
315+ ) ) }
316+ < Grid item >
317+ < AddMember nwid = { nwid } callback = { fetchData } />
318+ < AddGroup callback = { addGroup } />
319+ </ Grid >
202320 </ Grid >
203- </ Grid >
321+ </ DndProvider >
204322 </ AccordionDetails >
205323 </ Accordion >
206324 ) ;
0 commit comments