2020-01-18 01:27:56 +00:00
import React , { useState , useEffect , useReducer } from 'react' ;
2023-05-30 21:10:05 +00:00
import { Link , useParams , useHistory } from 'react-router-dom' ;
2020-01-11 02:50:55 +00:00
import './light.css' ;
2022-07-13 03:14:23 +00:00
import { Button , Container , Dropdown , Grid , Header , Icon , Image , Input , Item , Segment , Table } from 'semantic-ui-react' ;
2020-10-05 20:54:36 +00:00
import { statusColor , isAdmin , isInstructor , BasicTable , staticUrl , requester } from './utils.js' ;
2022-07-13 03:14:23 +00:00
import { NotFound } from './Misc.js' ;
2020-08-13 22:24:04 +00:00
import { AdminMemberInfo , AdminMemberPause , AdminMemberForm , AdminMemberCards , AdminMemberTraining , AdminMemberCertifications } from './AdminMembers.js' ;
2020-02-22 05:22:39 +00:00
import { AdminMemberTransactions } from './AdminTransactions.js' ;
2023-06-03 18:56:01 +00:00
import { StorageButton } from './Storage.js' ;
2021-11-12 01:03:10 +00:00
import AbortController from 'abort-controller' ;
2020-01-11 02:50:55 +00:00
2021-07-24 01:17:58 +00:00
const memberSorts = {
recently _vetted : 'Recently Vetted' ,
2021-11-12 04:16:19 +00:00
last _scanned : 'Last Scanned' ,
2022-11-16 18:32:50 +00:00
pinball _score : 'Pinball Score' ,
2021-11-12 03:30:08 +00:00
newest _active : 'Newest' ,
//newest_overall: 'Newest Overall',
oldest _active : 'Oldest' ,
//oldest_overall: 'Oldest Overall',
2021-11-17 04:50:39 +00:00
recently _inactive : 'Recently Inactive' ,
2021-11-12 03:30:08 +00:00
is _director : 'Directors' ,
is _instructor : 'Instructors' ,
due : 'Due' ,
overdue : 'Overdue' ,
2023-06-03 19:02:58 +00:00
storage : 'Storage' ,
2021-11-17 04:50:39 +00:00
everyone : 'Everyone' ,
2021-07-24 01:17:58 +00:00
} ;
2020-01-31 23:47:52 +00:00
export function MembersDropdown ( props ) {
2023-05-30 20:36:08 +00:00
const { token , name , onChange , value , initial , autofocus } = props ;
2020-01-31 23:47:52 +00:00
const [ response , setResponse ] = useState ( { results : [ ] } ) ;
2022-08-22 20:17:43 +00:00
const searchDefault = { seq : 0 , q : initial || '' , sort : 'newest_active' } ;
2020-01-31 23:47:52 +00:00
const [ search , setSearch ] = useState ( searchDefault ) ;
useEffect ( ( ) => {
requester ( '/search/' , 'POST' , token , search )
. then ( res => {
if ( ! search . seq || res . seq > response . seq ) {
setResponse ( res ) ;
}
} )
. catch ( err => {
console . log ( err ) ;
} ) ;
} , [ search ] ) ;
const options = response . results . map ( ( x , i ) => ( {
key : x . member . id ,
value : x . member . id ,
text : x . member . preferred _name + ' ' + x . member . last _name ,
image : { avatar : true , src : x . member . photo _small ? staticUrl + '/' + x . member . photo _small : '/nophoto.png' } ,
} ) ) ;
return (
< Dropdown
clearable
fluid
selection
search
name = { name }
options = { options }
value = { value }
placeholder = 'Search for Member'
onChange = { onChange }
2022-08-22 20:17:43 +00:00
onSearchChange = { ( e , v ) => setSearch ( { seq : parseInt ( e . timeStamp ) , q : v . searchQuery , sort : 'newest_active' } ) }
2023-05-30 20:36:08 +00:00
searchInput = { { autoFocus : autofocus } }
openOnFocus = { ! autofocus }
2020-01-31 23:47:52 +00:00
/ >
) ;
} ;
2021-11-17 04:50:39 +00:00
let responseCache = false ;
let pageCache = 0 ;
let sortCache = '' ;
let searchCache = '' ;
const loadMoreStrings = [
'Load More' ,
'Load EVEN More' ,
'Load WAY More' ,
'Why did you stop? LOAD MORE!' ,
'GIVE ME MORE NAMES!!' ,
'Shower me with names, baby' ,
'I don\'t care about the poor server, MORE NAMES!' ,
'Names make me hotter than two rats in a wool sock' ,
'Holy shit, I can\'t get enough names' ,
'I don\'t have anything better to do than LOAD NAMES!' ,
'I need names because I love N̶a̸M̸E̵S̴ it\'s not to late to stop but I can\'t because it feels so good god help me' ,
'The One who loads the names will liquify the NERVES of the sentient whilst I o̴̭̐b̴̙̾s̷̺͝ē̶̟r̷̦̓v̸͚̐ę̸̈́ ̷̞̒t̸͘ͅh̴͂͜e̵̜̕i̶̾͜r̷̃͜ ̵̹͊Ḷ̷͝Ȍ̸͚Ä̶̘́D̴̰́I̸̧̚N̵͖̎G̷̣͒' ,
'The Song of Names will will e̶̟̤͋x̷̜̀͘͜t̴̳̀i̸̪͑̇n̷̘̍g̵̥̗̓ṳ̴̑̈́i̷͚̿s̸̨̪̓ḣ̶̡̓ ̷̲͊ṫ̴̫h̸̙͕͗ḛ̸̡̃̈́ ̷̘̫̉̏v̸̧̟͗̕o̴͕̾͜i̷̢͛̿ͅc̴͕̥̈́̂ȅ̵͕s̶̹͋̀ ̶̰́͜͠ǒ̷̰̯f̵̛̥̊ ̸̟̟̒͝m̸̯̀̂o̶̝͛̌͜r̸̞̀ṫ̴̥͗ä̶̢́l̶̯̄͘ ̵̫̈́m̷̦̑̂ą̶͕͝ṋ̴̎͝ from the sphere I can see it can you see it it is beautiful' ,
'The final suffering of T̷̯̂͝H̴̰̏̉Ḛ̸̀̓ ̷̟̒ͅN̷̠̾Ą̵̟̈́M̶̡̾͝E̸̥̟̐͐S̸̖̍ are lies all is lost the pony he come h̷̲̺͂̾͒̔͝ḙ̶̻͒͠ ̷̙̘͈̬̰̽̽̈́̒͘c̵͎̺̞̰͝ơ̷͚̱̺̰̺͐̏͑͠m̴̖̰̓̈͝ĕ̷̜s̶̛̹̤̦͉̓͝ the í̵̠̞̙̦̱̠̅̊͒̌͊̓͠͠c̴̻̺̙͕̲͚͔̩̥͑ḩ̷̦̰̠̯̳̖̘́̉̾̾͠o̴͈̯̟̣̲͙̦̖̖͍̞̞̻̎͐̊͊̇͋̒͛̅͆̌͂̈̕r̷̡̝̲̜͇͉̣̹̖͕̻̐̑̉̋͋̉͒͋̍́̒͐͐͘ͅ ̵̳̖͕̩̝̮͈̻̣̤͎̟͓̜̄̿̓̈́p̴̰̝͓̣͍̫̞͓̑͌͊͑̓̂̽͑͝e̶̛̪̜̐̋́̆͊͌̋̄́͘r̶̫̬͈͌̔̽m̶̛̱̣͍͌̈́͋̾̈̀͑̽̋̏̊͋͝ę̶̋̀̈̃͠ą̵̡̣̫̮͙͈͚̞̰̠̥͇̣̽̿̉́̔̒͌̓͌̂̌̕͜͠t̷̯͚̭̮̠̐͋͆́͛̿́̏̆̚ě̶̢̨̩̞ş̸̢͍̱̻͕̪̗̻͖͇̱̳̽̈́̚͠ ̴͉̝̖̤͚̖̩̻̪̒ͅà̸̙̥̩̠̝̪̰͋́̊̓͌́͒̕͝ĺ̵̖̖͚̱͎̤̟̲̺͎͑͋̐̈́̓͂͆̅̈́̎̆̋̇l̸̢̧̟͉̞͇̱͉̙͇͊̏͐͠ͅ' ,
] ;
2020-02-17 00:19:44 +00:00
2020-01-11 02:50:55 +00:00
export function Members ( props ) {
2021-11-17 04:50:39 +00:00
const [ response , setResponse ] = useState ( responseCache ) ;
const [ loading , setLoading ] = useState ( false ) ;
const [ page , setPage ] = useState ( pageCache ) ;
const [ sort , setSort ] = useState ( sortCache ) ;
const [ search , setSearch ] = useState ( searchCache ) ;
2021-11-12 01:03:10 +00:00
const [ controller , setController ] = useState ( false ) ;
2021-11-12 04:16:19 +00:00
const { token , user } = props ;
2023-05-30 21:10:05 +00:00
const history = useHistory ( ) ;
2020-01-11 02:50:55 +00:00
2021-11-17 04:50:39 +00:00
const makeRequest = ( { loadPage , q , sort _key } ) => {
let pageNum = 0 ;
if ( loadPage ) {
pageNum = page + 1 ;
setPage ( pageNum ) ;
pageCache = pageNum ;
2021-11-12 01:03:10 +00:00
} else {
2021-11-12 03:30:08 +00:00
setResponse ( false ) ;
2021-11-17 04:50:39 +00:00
setPage ( 0 ) ;
pageCache = 0 ;
2021-11-12 01:03:10 +00:00
}
if ( controller ) {
controller . abort ( ) ;
}
const ctl = new AbortController ( ) ;
setController ( ctl ) ;
const signal = ctl . signal ;
2021-11-17 04:50:39 +00:00
const data = { page : pageNum } ;
if ( q ) data . q = q ;
if ( sort _key ) data . sort = sort _key ;
2021-11-12 01:03:10 +00:00
requester ( '/search/' , 'POST' , token , data , signal )
2020-01-11 02:50:55 +00:00
. then ( res => {
2021-11-17 04:50:39 +00:00
const r = loadPage ? { ... response , results : [ ... response . results , ... res . results ] } : res ;
setResponse ( r ) ;
responseCache = r ;
setLoading ( false ) ;
2020-01-11 02:50:55 +00:00
} )
. catch ( err => {
2021-11-17 04:50:39 +00:00
console . log ( 'Aborted.' ) ;
2020-01-11 02:50:55 +00:00
} ) ;
2021-11-17 04:50:39 +00:00
}
const loadMore = ( ) => {
setLoading ( true ) ;
makeRequest ( { loadPage : true , q : search , sort _key : sort } ) ;
} ;
const doSort = ( sort _key ) => {
setSort ( sort _key ) ;
sortCache = sort _key ;
setSearch ( '' ) ;
searchCache = '' ;
makeRequest ( { loadPage : false , sort _key : sort _key } ) ;
} ;
const doSearch = ( q ) => {
if ( q ) {
setSearch ( q ) ;
searchCache = q ;
setSort ( '' ) ;
sortCache = '' ;
makeRequest ( { loadPage : false , q : q } ) ;
} else {
doSort ( 'recently_vetted' ) ;
}
} ;
const handleChange = ( event ) => {
const q = event . target . value ;
doSearch ( q ) ;
} ;
2020-01-11 02:50:55 +00:00
2021-11-12 03:30:08 +00:00
useEffect ( ( ) => {
2021-11-17 04:50:39 +00:00
if ( ! responseCache ) {
doSort ( 'recently_vetted' ) ;
}
} , [ ] ) ;
2021-11-12 03:30:08 +00:00
2020-01-11 02:50:55 +00:00
return (
< Container >
< Header size = 'large' > Member List < / H e a d e r >
2022-02-06 08:11:28 +00:00
< p > Search by name , email , Spacebar username , or member ID : < / p >
2020-03-29 02:46:19 +00:00
2020-01-11 02:50:55 +00:00
< Input autoFocus focus icon = 'search'
placeholder = 'Search...'
2021-11-12 01:03:10 +00:00
value = { search }
onChange = { handleChange }
2020-01-11 02:50:55 +00:00
aria - label = 'search products'
2020-01-11 06:27:30 +00:00
style = { { marginRight : '0.5rem' } }
2020-01-11 02:50:55 +00:00
/ >
2021-11-12 01:03:10 +00:00
{ search . length ?
2020-01-11 06:27:30 +00:00
< Button
content = 'Clear'
2021-11-12 01:03:10 +00:00
onClick = { ( ) => doSearch ( '' ) }
2020-01-11 06:27:30 +00:00
/ > : ' '
}
2021-07-24 01:17:58 +00:00
< p > < / p >
< p >
Sort by { ' ' }
{ Object . entries ( memberSorts ) . map ( ( x , i ) =>
2022-05-07 06:28:33 +00:00
< React . Fragment key = { x [ 0 ] } >
2021-11-17 04:50:39 +00:00
< a href = 'javascript:void(0)' onClick = { ( ) => doSort ( x [ 0 ] ) } > { x [ 1 ] } < / a >
2021-07-24 01:17:58 +00:00
{ i < Object . keys ( memberSorts ) . length - 1 && ', ' }
2022-05-07 06:28:33 +00:00
< / R e a c t . F r a g m e n t >
2021-07-24 01:17:58 +00:00
) } .
< / p >
2020-01-11 02:50:55 +00:00
< Header size = 'medium' >
2021-11-12 03:30:08 +00:00
{ search . length ? 'Search Results' : memberSorts [ sort ] }
2020-01-11 02:50:55 +00:00
< / H e a d e r >
2021-11-12 04:16:19 +00:00
{ sort === 'last_scanned' &&
( user . member . allow _last _scanned ?
< p > Hide yourself from this list on the < Link to = '/account' > Account Settings < / L i n k > p a g e . < / p >
2021-07-24 01:17:58 +00:00
:
2021-11-12 04:16:19 +00:00
< p > Participate in this list on the < Link to = '/account' > Account Settings < / L i n k > p a g e . < / p >
)
}
{ response ?
< >
2023-06-23 01:00:47 +00:00
< p > { response . total } result { response . total === 1 ? '' : 's' } : < / p >
2021-11-17 04:50:39 +00:00
2021-11-12 04:16:19 +00:00
< Item . Group unstackable divided >
2021-11-17 04:50:39 +00:00
{ ! ! response . results . length &&
response . results . map ( ( x , i ) =>
2021-11-12 04:16:19 +00:00
< Item key = { x . member . id } as = { Link } to = { '/members/' + x . member . id } >
< div className = 'list-num' > { i + 1 } < / d i v >
< Item . Image size = 'tiny' src = { x . member . photo _small ? staticUrl + '/' + x . member . photo _small : '/nophoto.png' } / >
< Item . Content verticalAlign = 'top' >
< Item . Header >
< Icon name = 'circle' color = { statusColor [ x . member . status ] } / >
{ x . member . preferred _name } { x . member . last _name }
< / I t e m . H e a d e r >
2022-11-16 18:32:50 +00:00
{ sort === 'pinball_score' ?
< >
2023-01-17 23:18:00 +00:00
< Item . Description > Score : { x . member . pinball _score . toLocaleString ( ) || 'Unknown' } < / I t e m . D e s c r i p t i o n >
2022-11-16 18:32:50 +00:00
< Item . Description > Rank : { i === 0 ? 'Pinball Wizard' : 'Not the Pinball Wizard' } < / I t e m . D e s c r i p t i o n >
< / >
:
< >
2022-11-27 16:10:33 +00:00
< Item . Description >
Shelf : { x . member . storage . length ?
2023-06-03 18:56:01 +00:00
x . member . storage . sort ( ( a , b ) => a . location == 'member_shelves' ? - 1 : 1 ) . map ( ( x , i ) =>
< StorageButton storage = { x } / >
2022-11-27 16:10:33 +00:00
)
:
'None'
}
< / I t e m . D e s c r i p t i o n >
2022-11-16 18:32:50 +00:00
< Item . Description > Joined : { x . member . application _date || 'Unknown' } < / I t e m . D e s c r i p t i o n >
< / >
}
2021-11-12 04:16:19 +00:00
< Item . Description > ID : { x . member . id } < / I t e m . D e s c r i p t i o n >
< / I t e m . C o n t e n t >
< / I t e m >
)
}
< / I t e m . G r o u p >
2021-11-17 04:50:39 +00:00
{ ! search && response . total !== response . results . length &&
< Button content = { loading ? 'Reticulating splines...' : loadMoreStrings [ page ] } onClick = { loadMore } disabled = { loading } / >
2021-11-12 04:16:19 +00:00
}
< / >
:
< p > Loading ... < / p >
2020-01-11 02:50:55 +00:00
}
< / C o n t a i n e r >
) ;
} ;
2020-02-17 00:19:44 +00:00
let resultCache = { } ;
2020-01-11 23:53:42 +00:00
export function MemberDetail ( props ) {
2020-02-17 00:19:44 +00:00
const { id } = useParams ( ) ;
const [ result , setResult ] = useState ( resultCache [ id ] || false ) ;
2020-01-18 01:27:56 +00:00
const [ refreshCount , refreshResult ] = useReducer ( x => x + 1 , 0 ) ;
2020-01-11 23:53:42 +00:00
const [ error , setError ] = useState ( false ) ;
2020-01-13 08:01:42 +00:00
const { token , user } = props ;
2020-01-11 23:53:42 +00:00
useEffect ( ( ) => {
requester ( '/search/' + id + '/' , 'GET' , token )
. then ( res => {
2020-01-14 00:52:15 +00:00
setResult ( res ) ;
2020-02-17 00:19:44 +00:00
resultCache [ id ] = res ;
2020-01-11 23:53:42 +00:00
} )
. catch ( err => {
console . log ( err ) ;
setError ( true ) ;
} ) ;
2020-01-18 01:27:56 +00:00
} , [ refreshCount ] ) ;
2020-01-11 23:53:42 +00:00
2020-01-14 00:52:15 +00:00
const member = result . member || false ;
2021-08-19 07:42:01 +00:00
const photo = member ? . photo _large || member ? . photo _small || false ;
2020-01-14 00:52:15 +00:00
2020-01-11 23:53:42 +00:00
return (
< Container >
{ ! error ?
member ?
< div >
< Header size = 'large' > { member . preferred _name } { member . last _name } < / H e a d e r >
2020-01-13 08:01:42 +00:00
< Grid stackable columns = { 2 } >
2020-10-05 20:54:36 +00:00
< Grid . Column width = { isAdmin ( user ) ? 8 : 5 } >
2020-01-13 08:01:42 +00:00
< p >
2021-08-19 07:42:01 +00:00
< Image rounded size = 'medium' src = { photo ? staticUrl + '/' + photo : '/nophoto.png' } / >
2020-01-13 08:01:42 +00:00
< / p >
{ isAdmin ( user ) ?
2020-01-18 01:27:56 +00:00
< AdminMemberInfo result = { result } refreshResult = { refreshResult } { ... props } / >
2020-01-13 08:01:42 +00:00
:
2020-05-01 03:54:23 +00:00
< React . Fragment >
< BasicTable >
< Table . Body >
< Table . Row >
< Table . Cell > Status : < / T a b l e . C e l l >
< Table . Cell >
< Icon name = 'circle' color = { statusColor [ member . status ] } / >
{ member . status || 'Unknown' }
< / T a b l e . C e l l >
< / T a b l e . R o w >
< Table . Row >
< Table . Cell > Joined : < / T a b l e . C e l l >
2020-10-05 21:30:06 +00:00
< Table . Cell > { member . application _date || 'Unknown' } < / T a b l e . C e l l >
2020-05-01 03:54:23 +00:00
< / T a b l e . R o w >
< Table . Row >
< Table . Cell > Public Bio : < / T a b l e . C e l l >
< / T a b l e . R o w >
< / T a b l e . B o d y >
< / B a s i c T a b l e >
< p className = 'bio-paragraph' >
{ member . public _bio || 'None yet.' }
< / p >
< / R e a c t . F r a g m e n t >
2020-01-13 08:01:42 +00:00
}
< / G r i d . C o l u m n >
2020-10-05 20:54:36 +00:00
< Grid . Column width = { isAdmin ( user ) ? 8 : 11 } >
{ isInstructor ( user ) && ! isAdmin ( user ) && < Segment padded >
< AdminMemberTraining result = { result } refreshResult = { refreshResult } { ... props } / >
< / S e g m e n t > }
2020-01-14 00:52:15 +00:00
{ isAdmin ( user ) && < Segment padded >
2020-01-18 01:27:56 +00:00
< AdminMemberForm result = { result } refreshResult = { refreshResult } { ... props } / >
2020-01-14 00:52:15 +00:00
< / S e g m e n t > }
2020-01-20 02:25:58 +00:00
{ isAdmin ( user ) && < Segment padded >
< AdminMemberPause result = { result } refreshResult = { refreshResult } { ... props } / >
< / S e g m e n t > }
2020-01-13 08:01:42 +00:00
< / G r i d . C o l u m n >
< / G r i d >
2020-01-15 00:12:50 +00:00
{ isAdmin ( user ) && < Segment padded >
2020-01-18 01:27:56 +00:00
< AdminMemberCards result = { result } refreshResult = { refreshResult } { ... props } / >
2020-01-15 00:12:50 +00:00
< / S e g m e n t > }
2020-08-13 22:24:04 +00:00
{ isAdmin ( user ) && < Segment padded >
< AdminMemberCertifications result = { result } refreshResult = { refreshResult } { ... props } / >
< / S e g m e n t > }
2020-08-08 22:49:03 +00:00
{ isAdmin ( user ) && < Segment padded >
< AdminMemberTraining result = { result } refreshResult = { refreshResult } { ... props } / >
< / S e g m e n t > }
2020-01-18 04:47:54 +00:00
{ isAdmin ( user ) && < Segment padded >
2020-02-22 05:22:39 +00:00
< AdminMemberTransactions result = { result } refreshResult = { refreshResult } { ... props } / >
2020-01-18 04:47:54 +00:00
< / S e g m e n t > }
2020-01-11 23:53:42 +00:00
< / d i v >
:
< p > Loading ... < / p >
:
< NotFound / >
}
< / C o n t a i n e r >
) ;
} ;