1- import { useEffect , useState } from 'react'
1+ import { useCallback , useContext , useEffect , useState } from 'react'
22import { useRecoilValue } from 'recoil'
33import activeGameState , { SelectedActiveGame } from '../state/activeGameState'
44import ErrorMessage from '../components/ErrorMessage'
55import { format } from 'date-fns'
6- import { IconPlus } from '@tabler/icons-react'
6+ import { IconPinned , IconPinnedFilled , IconPlus } from '@tabler/icons-react'
77import Button from '../components/Button'
88import TableCell from '../components/tables/TableCell'
99import TableBody from '../components/tables/TableBody'
@@ -17,6 +17,10 @@ import Identifier from '../components/Identifier'
1717import { useNavigate } from 'react-router-dom'
1818import routes from '../constants/routes'
1919import { PlayerGroup } from '../entities/playerGroup'
20+ import Tippy from '@tippyjs/react'
21+ import usePinnedGroups from '../api/usePinnedGroups'
22+ import ToastContext , { ToastType } from '../components/toast/ToastContext'
23+ import togglePinnedGroup from '../api/toggledPinnedGroup'
2024
2125export default function Groups ( ) {
2226 const activeGame = useRecoilValue ( activeGameState ) as SelectedActiveGame
@@ -27,8 +31,12 @@ export default function Groups() {
2731 const { groups, loading, error, mutate } = useGroups ( activeGame )
2832 const sortedGroups = useSortedItems ( groups , 'name' , 'asc' )
2933
34+ const { groups : pinnedGroups , mutate : mutatePinnedGroups } = usePinnedGroups ( activeGame )
35+
3036 const navigate = useNavigate ( )
3137
38+ const toast = useContext ( ToastContext )
39+
3240 useEffect ( ( ) => {
3341 if ( ! showModal ) setEditingGroup ( null )
3442 } , [ showModal , editingGroup ] )
@@ -42,6 +50,25 @@ export default function Groups() {
4250 navigate ( `${ routes . players } ?search=group:${ group . id } ` )
4351 }
4452
53+ const isPinned = useCallback ( ( group : PlayerGroup ) => {
54+ return pinnedGroups . find ( ( pg ) => pg . id === group . id )
55+ } , [ pinnedGroups ] )
56+
57+ const togglePinned = useCallback ( async ( group : PlayerGroup ) => {
58+ try {
59+ await togglePinnedGroup ( activeGame . id , group . id , ! isPinned ( group ) )
60+ mutatePinnedGroups ( ( data ) => ( {
61+ ...data ,
62+ groups : isPinned ( group )
63+ ? data ! . groups . filter ( ( pg ) => pg . id !== group . id )
64+ : [ ...data ! . groups , group ]
65+ } ) , false )
66+ toast . trigger ( isPinned ( group ) ? 'Group unpinned' : 'Group pinned to your dashboard' , ToastType . SUCCESS )
67+ } catch ( err ) {
68+ toast . trigger ( 'Failed to pin group' , ToastType . ERROR )
69+ }
70+ } , [ activeGame . id , isPinned , mutatePinnedGroups , toast ] )
71+
4572 return (
4673 < Page
4774 title = 'Groups'
@@ -70,7 +97,31 @@ export default function Groups() {
7097 { ( group ) => (
7198 < >
7299 < TableCell className = 'min-w-80' >
73- < Identifier id = { group . id } />
100+ < div className = 'flex flex-row items-center space-x-4' >
101+ { pinnedGroups &&
102+ < Tippy content = { < p > { isPinned ( group ) ? 'Unpin group' : 'Pin group to dashboard' } </ p > } >
103+ < div className = 'inline-block' >
104+ { isPinned ( group ) &&
105+ < Button
106+ variant = 'icon'
107+ className = 'p-1 rounded-full bg-indigo-900'
108+ icon = { < IconPinnedFilled /> }
109+ onClick = { ( ) => togglePinned ( group ) }
110+ />
111+ }
112+ { ! isPinned ( group ) &&
113+ < Button
114+ variant = 'icon'
115+ className = 'p-1 rounded-full bg-indigo-900'
116+ icon = { < IconPinned /> }
117+ onClick = { ( ) => togglePinned ( group ) }
118+ />
119+ }
120+ </ div >
121+ </ Tippy >
122+ }
123+ < Identifier id = { group . id } />
124+ </ div >
74125 </ TableCell >
75126 < TableCell className = 'min-w-[320px] max-w-[320px] lg:min-w-0' >
76127 { group . name }
0 commit comments