Skip to content

Commit

Permalink
#865 Write projects overview in react/javascript
Browse files Browse the repository at this point in the history
* move Table to projects/assets/js/components
* work on Table sort, config store and Projects
  • Loading branch information
CalamityC committed Jan 16, 2024
1 parent 3c9cee4 commit bc590bf
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 71 deletions.
46 changes: 46 additions & 0 deletions rdmo/core/assets/js/components/SearchAndFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import PropTypes from 'prop-types'

const TextField = ({ value, onChange, placeholder }) => {
return (
<div className="form-group mb-0">
<div className="input-group">
<input type="text" className="form-control" placeholder={placeholder}
value={ value } onChange={e => onChange(e.target.value)}></input>
<span className="input-group-btn">
<button className="btn btn-default" onClick={() => onChange('')}>
<span className="fa fa-times"></span>
</button>
</span>
</div>
</div>
)
}

TextField.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string.isRequired
}

const Select = ({ value, options, onChange, placeholder }) => {
return (
<div className="form-group mb-0">
<select className="form-control" value={value} onChange={e => onChange(e.target.value)}>
<option value="">{placeholder}</option>
{
options.map((option, index) => <option value={option} key={index}>{option}</option>)
}
</select>
</div>
)
}

Select.propTypes = {
value: PropTypes.string,
options: PropTypes.array.isRequired,
onChange: PropTypes.func,
placeholder: PropTypes.string
}

export { TextField, Select }
5 changes: 5 additions & 0 deletions rdmo/projects/assets/js/actions/configActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { UPDATE_CONFIG } from './types'

export function updateConfig(path, value) {
return {type: UPDATE_CONFIG, path, value}
}
7 changes: 3 additions & 4 deletions rdmo/projects/assets/js/actions/projectsActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import ProjectsApi from '../api/ProjectsApi'
import { FETCH_PROJECTS_ERROR, FETCH_PROJECTS_INIT, FETCH_PROJECTS_SUCCESS }
from './types'
// const MY_PROJECTS = 'projects/myProjects'

// export function fetchAllProjects() {
// return function(dispatch) {
Expand Down Expand Up @@ -50,6 +49,6 @@ export function fetchProjectsError(error) {
return {type: FETCH_PROJECTS_ERROR, error}
}

export function updateConfig(path, value) {
return {type: 'projects/updateConfig', path, value}
}
// export function updateConfig(path, value) {
// return {type: 'projects/updateConfig', path, value}
// }
1 change: 1 addition & 0 deletions rdmo/projects/assets/js/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const FETCH_PROJECTS_INIT = 'projects/fetchProjectsInit'
export const FETCH_CURRENT_USER_INIT = 'currentUser/fetchCurrentUserInit'
export const FETCH_CURRENT_USER_SUCCESS = 'currentUser/fetchCurrentUserSuccess'
export const FETCH_CURRENT_USER_ERROR = 'currentUser/fetchCurrentUserError'
export const UPDATE_CONFIG = 'config/updateConfig'
157 changes: 157 additions & 0 deletions rdmo/projects/assets/js/components/helper/Table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash'

const Table = ({
cellFormatters,
columnWidths,
data,
headerFormatters,
initialRows = 3,
rowsToLoad = 2,
sortableColumns,
/* order of elements in 'visibleColumns' corresponds to order of columns in table */
visibleColumns,
configActions,
config
}) => {

const displayedRows = get(config, 'table.rows', 0)
displayedRows == 0 && configActions.updateConfig('table.rows', initialRows)
const { column: sortColumn, order: sortOrder } = get(config, 'table.sort', '')
// const sortColumn = get(config, 'table.sort.column', '')
// const sortOrder = get(config, 'table.sort.order', '')
console.log('sortColumn', sortColumn)
const loadMore = () => {
configActions.updateConfig('table.rows', parseInt(displayedRows) + parseInt(rowsToLoad))
}

const handleHeaderClick = column=> {
if (sortableColumns.includes(column)) {
if (sortColumn === column) {
// configActions.updateConfig('table.sort', { column: column, order: sortOrder === 'asc' ? 'desc' : 'asc' })
configActions.updateConfig('table.sort.column', column)
configActions.updateConfig('table.sort.order', sortOrder === 'asc' ? 'desc' : 'asc')
} else {
// configActions.updateConfig('table.sort', { column: column, order: 'asc'})
configActions.updateConfig('table.sort.column', column)
configActions.updateConfig('table.sort.order', 'asc')
}
}
}

const sortedData = () => {
// const slicedData = data //.slice(0, displayedRows)
const sorted = data
if (sortColumn) {
// const sorted = slicedData
console.log('sorted', sorted)

sorted.sort((a, b) => {
let valueA = a[sortColumn]
let valueB = b[sortColumn]
const sortRawContent = (
headerFormatters[sortColumn]?.sortRawContent ?? true
)

if (!sortRawContent) {
valueA = formatCellContent(a, sortColumn, a[sortColumn])
valueB = formatCellContent(b, sortColumn, b[sortColumn])
}

if (sortOrder === 'asc') {
return valueA.localeCompare ? valueA.localeCompare(valueB) : valueA - valueB
} else {
return valueB.localeCompare ? valueB.localeCompare(valueA) : valueB - valueA
}
})
}
// return sorted.slice(0, displayedRows)
return sorted
}

const renderSortIcon = (column) => {
const isSortColumn = sortColumn === column
const isAsc = sortOrder === 'asc'

return (
<span className="ml-5 sort-icon">
<i className={`fa fa-sort${isSortColumn ? isAsc ? '-asc' : '-desc' : ''} ${isSortColumn ? '' : 'text-muted'}`} />
</span>
)
}

const renderHeaders = () => {
return (
<thead className="thead-dark">
<tr>
{visibleColumns.map((column, index) => {
const headerFormatter = headerFormatters[column]
const columnHeaderContent = headerFormatter && headerFormatter.render ? headerFormatter.render(column) : column

return (
<th key={column} style={{ width: columnWidths[index] }} onClick={() => handleHeaderClick(column)}>
{columnHeaderContent}
{sortableColumns.includes(column) && renderSortIcon(column)}
</th>
)
})}
</tr>
</thead>
)
}

const formatCellContent = (row, column, content) => {
if (cellFormatters && cellFormatters[column] && typeof cellFormatters[column] === 'function') {
return cellFormatters[column](content, row)
}
return content
}

const renderRows = () => {
const sortedRows = sortedData().slice(0, displayedRows)
// const sortedRows = sortedData()
return (
<tbody>
{sortedRows.map((row, index) => (
<tr key={index}>
{visibleColumns.map((column, index) => (
<td key={column}style={{ width: columnWidths[index] }}>
{formatCellContent(row, column, row[column])}
</td>
))}
</tr>
))}
</tbody>
)
}

return (
<div className="table-container">
<table className="table table-borderless">
{renderHeaders()}
{renderRows()}
</table>
{displayedRows < data.length && (
<button onClick={loadMore} className="load-more-btn">
{gettext('Load More')}
</button>
)}
</div>
)
}

Table.propTypes = {
cellFormatters: PropTypes.object,
columnWidths: PropTypes.arrayOf(PropTypes.string),
data: PropTypes.arrayOf(PropTypes.object).isRequired,
headerFormatters: PropTypes.object,
initialRows: PropTypes.number,
rowsToLoad: PropTypes.number,
sortableColumns: PropTypes.arrayOf(PropTypes.string),
visibleColumns: PropTypes.arrayOf(PropTypes.string),
configActions: PropTypes.object,
config: PropTypes.object
}

export default Table
Loading

0 comments on commit bc590bf

Please sign in to comment.