Skip to content

Instantly share code, notes, and snippets.

@tannerlinsley
Created March 15, 2019 07:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tannerlinsley/6077d14bf3311500632816b91ba4152d to your computer and use it in GitHub Desktop.
Save tannerlinsley/6077d14bf3311500632816b91ba4152d to your computer and use it in GitHub Desktop.
import React, { useReducer, useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import { createSelector } from 'reselect'
import { connect } from 'react-redux'
import ReactTable from 'react-table'
import 'react-table/react-table.css'
import includes from 'ramda/src/includes'
import { DEBOUNCE_DELAY } from '../../../reference/constants'
import { loadContacts } from '../../../actions/v2/contactsActions'
import { useDebounce } from '../../../reference/hooks'
import TableControls from '../tablecontrols/TableControls'
import ContactsTableContainer from './ContactsTableContainer'
import ContactAvatar from '../ContactAvatar'
import './ContactsTable.css'
import equals from 'ramda/src/equals'
const getFilteredColumns = createSelector(
[(fields, columns) => fields, (fields, columns) => columns],
(fields, columns) => fields
.filter(f => includes(f.field, columns))
.map(c => ({
Header: c.label,
id: c.field,
accessor: `metadata.fields.${c.field}`,
}))
)
const reducer = ({ initial, ...state}, action) => {
switch (action.type) {
case 'ADD_LIST':
return { ...state, page: 0, lists: [...state.lists, action.listId] }
case 'REMOVE_LIST':
return { ...state, page: 0, lists: state.lists.filter(l => l !== action.listId) }
case 'ADD_COLUMN':
return { ...state, columns: [...state.columns, action.column] }
case 'REMOVE_COLUMN':
return { ...state, columns: state.columns.filter(c => c !== action.column) }
case 'ADD_RELATIONSHIP':
return { ...state, page: 0, relationships: [...state.relationships, action.relationship] }
case 'REMOVE_RELATIONSHIP':
return { ...state, page: 0, relationships: state.relationships.filter(r => r !== action.relationship) }
case 'ON_SORTED_CHANGE':
return { ...state, page: 0, sorted: action.sorted }
case 'ON_PAGE_CHANGE':
return { ...state, page: action.page }
case 'ON_PAGE_SIZE_CHANGE':
return { ...state, page: action.page, pageSize: action.pageSize }
case 'ON_FILTERED_CHANGE':
return { ...state, page: 0, filtered: action.filtered }
case 'RESET_TABLE':
return action.resetState
default:
throw new Error()
}
}
const ContactsTable = React.memo(({
loadContacts,
contacts,
loading,
pages,
total,
fields,
tableRowProps,
tableClassName,
initialColumns,
additionalTableControl,
tableHeader,
tableControlRight,
}) => {
const initialState = {
lists: JSON.parse(window.sessionStorage.getItem('ct-lists')) || [],
columns: JSON.parse(window.sessionStorage.getItem('ct-columns')) || initialColumns,
relationships: JSON.parse(window.sessionStorage.getItem('ct-relationships')) || [],
sorted: JSON.parse(window.sessionStorage.getItem('ct-sorted')) || [{ id: 'last_name' }, { id: 'first_name' }],
page: JSON.parse(window.sessionStorage.getItem('ct-page')) || 0,
pageSize: JSON.parse(window.sessionStorage.getItem('ct-pageSize')) || 50,
filtered: JSON.parse(window.sessionStorage.getItem('ct-filtered')) || [],
}
const [state, dispatch] = useReducer(reducer, initialState)
const [tableControl, updateTableControl] = useState('')
const fetchContacts = () => {
const settings = {
lists: state.lists,
page: state.page + 1,
per: state.pageSize,
filter: state.filtered,
sort: state.sorted,
relationships: state.relationships,
}
loadContacts(settings)
.then(({ ok }) => {
if (ok) {
const elem = document.getElementById('contacts-table-body')
if (elem) elem.scrollTop = 0
}
})
}
const filteredDebounced = useDebounce(state.filtered, DEBOUNCE_DELAY)
useEffect(() => {
fetchContacts()
}, [
state.lists,
state.page,
state.pageSize,
state.sorted,
state.relationships,
])
useEffect(() => {
if(!state.initial) {
fetchContacts()
}
}, [
filteredDebounce
])
const addList = listId => dispatch({ type: 'ADD_LIST', listId })
const removeList = listId => dispatch({ type: 'REMOVE_LIST', listId })
useEffect(() => {
window.sessionStorage.setItem('ct-lists', JSON.stringify(state.lists))
}, [state.lists])
const addColumn = column => dispatch({ type: 'ADD_COLUMN', column })
const removeColumn = column => dispatch({ type: 'REMOVE_COLUMN', column })
// incomplete and doesn't work
const initialColumnUpdate = useRef(true)
const initialColumnsRef = useRef([])
useEffect(() => {
if (initialColumnUpdate.current) {
initialColumnUpdate.current = false
initialColumnsRef.current = state.columns
return
}
if (equals(initialColumnsRef.current, state.columns)) {
return
}
window.sessionStorage.setItem('ct-columns', JSON.stringify(state.columns))
}, [state.columns])
const addRelationship = relationship => dispatch({ type: 'ADD_RELATIONSHIP', relationship })
const removeRelationship = relationship => dispatch({ type: 'REMOVE_RELATIONSHIP', relationship })
useEffect(() => {
window.sessionStorage.setItem('ct-relationships', JSON.stringify(state.relationships))
}, [state.relationships])
const onSortedChange = sorted => dispatch({ type: 'ON_SORTED_CHANGE', sorted })
useEffect(() => {
window.sessionStorage.setItem('ct-sorted', JSON.stringify(state.sorted))
}, [state.sorted])
const onPageChange = page => dispatch({ type: 'ON_PAGE_CHANGE', page })
useEffect(() => {
window.sessionStorage.setItem('ct-page', JSON.stringify(state.page))
}, [state.page])
const onPageSizeChange = (pageSize, page) => dispatch({ type: 'ON_PAGE_SIZE_CHANGE', pageSize, page })
useEffect(() => {
window.sessionStorage.setItem('ct-pageSize', JSON.stringify(state.pageSize))
}, [state.pageSize])
const onFilteredChange = filtered => dispatch({ type: 'ON_FILTERED_CHANGE', filtered })
useEffect(() => {
window.sessionStorage.setItem('ct-filtered', JSON.stringify(state.filtered))
}, [state.filtered])
const closeTableControl = () => updateTableControl('')
const showTableControl = control => updateTableControl(prevControl => prevControl === control ? '' : control)
const resetTable = () => {
const resetState = {
initial: true,
lists: [],
columns: initialColumns,
relationships: [],
sorted: [{ id: 'last_name' }, { id: 'first_name' }],
page: 0,
pageSize: 50,
filtered: [],
}
dispatch({ type: 'RESET_TABLE', resetState })
window.sessionStorage.removeItem('ct-lists')
window.sessionStorage.removeItem('ct-columns')
window.sessionStorage.removeItem('ct-relationships')
window.sessionStorage.removeItem('ct-sorted')
window.sessionStorage.removeItem('ct-page')
window.sessionStorage.removeItem('ct-pageSize')
window.sessionStorage.removeItem('ct-filtered')
}
const getTheadFilterProps = () => state.relationships.length ? { className: 'disabled', title: 'disabled when relationships are selected.' } : {}
const getTbodyProps = () => ({ id: 'contacts-table-body' })
const selectedColumns = getFilteredColumns(fields, state.columns)
const getAvatar = row => <ContactAvatar contact={row.original} style={{ position: 'relative', top: '50%', transform: 'translateY(-50%)' }}/>
return (
<section className="contacts-table-container">
{tableHeader && tableHeader({ showTableControl: showTableControl })}
<TableControls
resetTable={resetTable}
showTableControl={showTableControl}
selectedLists={state.lists}
selectedColumns={state.columns}
selectedRelationships={state.relationships}
close={closeTableControl}
tableControl={tableControl}
addList={addList}
addColumn={addColumn}
removeList={removeList}
removeColumn={removeColumn}
addRelationship={addRelationship}
removeRelationship={removeRelationship}
// used for table control right
tableControlRight={tableControlRight}
filtered={state.filtered}
lists={state.lists}
relationships={state.relationships}
total={total}
additionalTableControl={additionalTableControl({ close: closeTableControl, tableControl, fetchContacts: fetchContacts })}
/>
<ContactsTableContainer slideDown={tableControl}>
<ReactTable
manual
data={contacts}
pages={pages}
loading={loading}
className={tableClassName}
// Controlled props
sorted={state.sorted}
page={state.page}
pageSize={state.pageSize}
filtered={state.filtered}
// Callbacks
onSortedChange={onSortedChange}
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
onFilteredChange={onFilteredChange}
filterable
getTrProps={tableRowProps}
// disable TheadFilter controls when relationships are selected
getTheadFilterProps={getTheadFilterProps}
getTbodyProps={getTbodyProps}
columns={[
{
accessor: 'picture',
Cell: getAvatar,
width: 67,
height: 50,
style: { margin: 'inherit' },
sortable: false,
filterable: false,
},
...selectedColumns,
]}
/>
</ContactsTableContainer>
</section>
)
})
ContactsTable.propTypes = {
loadContacts: PropTypes.func.isRequired,
contacts: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired,
pages: PropTypes.number.isRequired,
total: PropTypes.number.isRequired,
fields: PropTypes.array.isRequired,
tableRowProps: PropTypes.func.isRequired,
tableClassName: PropTypes.string.isRequired,
initialColumns: PropTypes.array.isRequired,
additionalTableControl: PropTypes.func.isRequired,
tableHeader: PropTypes.func,
tableControlRight: PropTypes.func,
}
ContactsTable.defaultProps = {
initialColumns: ['first_name', 'last_name', 'position', 'phone_number', 'year', 'institute'],
additionalTableControl: () => null,
}
const mapStateToProps = state => ({
contacts: state.contacts.contacts,
pages: state.contacts.pages,
total: state.contacts.total,
loading: state.contacts.loading,
fields: state.widFields.fields,
})
export default connect(mapStateToProps, { loadContacts })(ContactsTable)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment