Skip to content

Commit

Permalink
Allow user sort (#1075)
Browse files Browse the repository at this point in the history
* Allow user to sort issue #1013 Fix

* added pmd file to root

* fix sorting issue in task table
  • Loading branch information
Precious-Macaulay authored Feb 24, 2024
1 parent 09d3636 commit 4e1b763
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 31 deletions.
177 changes: 146 additions & 31 deletions frontend/src/components/task/task-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,121 @@ const styles = theme => ({
},
})

const tableHeaderMetadata = {
"task.table.head.task": { sortable: true, numeric: false, dataBaseKey: "title" },
"task.table.head.status": { sortable: true, numeric: false, dataBaseKey: "status" },
"task.table.head.project": { sortable: true, numeric: false, dataBaseKey: "Project.name" },
"task.table.head.value": { sortable: true, numeric: true, dataBaseKey: "value" },
"task.table.head.labels": { sortable: true, numeric: false, dataBaseKey: "Labels" },
"task.table.head.createdAt": { sortable: true, numeric: false, dataBaseKey: "createdAt" }
}

const getSortingValue = (item, fieldId) => {

const getValue = (item, dataBaseKey) => {
const keys = dataBaseKey.split(".");
return keys.reduce((obj, key) => (obj && obj[key] !== 'undefined') ? obj[key] : undefined, item);
};

const metadata = tableHeaderMetadata[fieldId];
if (!metadata) {
console.error(`No metadata found for fieldId: ${fieldId}`);
return null;
}

const { numeric, dataBaseKey } = metadata;

const value = getValue(item, dataBaseKey);

if (value === undefined) {
console.error(`Failed to get value for fieldId: ${fieldId}`);
return null;
}

if (numeric) {
const parsedValue = parseFloat(value);
if (isNaN(parsedValue)) {
console.error(`Failed to parse numeric value for fieldId: ${fieldId}`);
return null;
}
return parsedValue;
}
return value;
};

const sortData = (data, sortedBy, sortDirection) => {
if (sortDirection === 'none') return data;

return [...data].sort((a, b) => {
let aValue = getSortingValue(a, sortedBy);
let bValue = getSortingValue(b, sortedBy);

// Handle null values
if (aValue === null || bValue === null) {
return (aValue === null ? (sortDirection === 'asc' ? -1 : 1) : (sortDirection === 'asc' ? 1 : -1));
}

// Handle date sorting
if (sortedBy === 'task.table.head.createdAt') {
let aDate = new Date(aValue).getTime();
let bDate = new Date(bValue).getTime();
return (sortDirection === 'asc' ? aDate - bDate : bDate - aDate);
}

// Handle labels array sorting
if (sortedBy === 'task.table.head.labels') {
aValue = aValue.map(label => label.name).join('');
bValue = bValue.map(label => label.name).join('');
}

// Handle string sorting
let comparator = String(aValue).localeCompare(String(bValue), 'en', { numeric: true, sensitivity: 'base', ignorePunctuation: true });
return (sortDirection === 'asc' ? comparator : -comparator);
});
};

class CustomPaginationActionsTable extends React.Component {
constructor(props) {
super(props)

this.state = {
page: 0,
rowsPerPage: 10,
sortedBy: null,
sortDirection: 'asc',
sortedData: this.props.tasks.data
}
}

componentDidUpdate(prevProps) {
if (prevProps.tasks !== this.props.tasks) {
const { sortedBy, sortDirection } = this.state;
const newSortedData = sortData(this.props.tasks.data, sortedBy, sortDirection);
this.setState({
sortedData: newSortedData
});
}
}

handleSort = (fieldId, sortDirection) => {
const newSortedData = sortData(this.props.tasks.data, fieldId, sortDirection);

return {
sortedBy: fieldId,
sortDirection,
sortedData: newSortedData,
};
}

sortHandler = (fieldId) => {
this.setState((prevState) => {
const { sortedBy, sortDirection } = prevState;
const newSortDirection = sortedBy === fieldId ? (sortDirection === 'asc' ? 'desc' : (sortDirection === 'desc' ? 'none' : 'asc')) : 'asc';
return this.handleSort(fieldId, newSortDirection);
});
};


handleChangePage = (event, page) => {
this.setState({ page })
};
Expand All @@ -188,49 +293,59 @@ class CustomPaginationActionsTable extends React.Component {

render() {
const { classes, tasks } = this.props
const { rowsPerPage, page } = this.state
const emptyRows = tasks.data.length ? rowsPerPage - Math.min(rowsPerPage, tasks.data.length - page * rowsPerPage) : 0
const { rowsPerPage, page, sortedBy, sortDirection, sortedData } = this.state;

const emptyRows = sortedData.length ? rowsPerPage - Math.min(rowsPerPage, sortedData.length - page * rowsPerPage) : 0
const TableCellWithSortLogic = ({ fieldId, defineMessage, sortHandler }) => {
return (
<TableSortLabel
active={fieldId === sortedBy && sortDirection !== 'none'}
direction={sortDirection}
onClick={
() => {
return sortHandler(fieldId)
}
}
>
<FormattedMessage id={fieldId} defineMessage={defineMessage} />
</TableSortLabel>
)
}

const TableHeadCustom = () => {
console.log()
return (
<TableHead>
<TableRow>
{Object.entries(tableHeaderMetadata).map(([fieldId, metadata]) => (
<TableCell key={fieldId}>
<TableCellWithSortLogic sortHandler={this.sortHandler} fieldId={fieldId} defaultMessage={metadata.dataBaseKey} />
</TableCell>
))}
</TableRow>
</TableHead>
);
};


if (tasks.completed && tasks.data.length === 0) {
<Paper className={classes.root}>
return (<Paper className={classes.root}>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 200 }}>
<Typography variant='caption'>
<FormattedMessage id='task.table.body.noIssues' defaultMessage='No issues' />
</Typography>
</div>
</Paper>
</Paper>);
}

return (
<Paper className={classes.root}>
<ReactPlaceholder style={{ marginBottom: 20, padding: 20 }} showLoadingAnimation type='text' rows={12} ready={tasks.completed}>
<div className={classes.tableWrapper}>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell>
<FormattedMessage id='task.table.head.task' defaultMessage='Task' />
</TableCell>
<TableCell>
<FormattedMessage id='task.table.head.status' defaultMessage='Status' />
</TableCell>
<TableCell>
<FormattedMessage id='task.table.head.project' defaultMessage='Project' />
</TableCell>
<TableCell>
<FormattedMessage id='task.table.head.value' defaultMessage='Value' />
</TableCell>
<TableCell>
<FormattedMessage id='task.table.head.labels' defaultMessage='Labels' />
</TableCell>
<TableCell>
<FormattedMessage id='task.table.head.createdAt' defaultMessage='Created' />
</TableCell>
</TableRow>
</TableHead>
<TableHeadCustom />
<TableBody>
{tasks.data.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(n => {
{sortedData.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(n => {
const assigned = n.Assigns.find(a => a.id === n.assigned)
const assignedUser = assigned && assigned.User
return (
Expand Down Expand Up @@ -298,11 +413,11 @@ class CustomPaginationActionsTable extends React.Component {
<TableRow>
<TablePagination
colSpan={3}
count={tasks.data.length}
count={sortedData.length}
rowsPerPage={rowsPerPage}
page={page}
onChangePage={(e, page) => this.handleChangePage(e, page)}
onChangeRowsPerPage={(e, page) => this.handleChangeRowsPerPage(e, page)}
onPageChange={(e, page) => this.handleChangePage(e, page)}
onRowsPerPageChange={(e, page) => this.handleChangeRowsPerPage(e, page)}
Actions={TablePaginationActionsWrapped}
/>
</TableRow>
Expand All @@ -322,4 +437,4 @@ CustomPaginationActionsTable.propTypes = {
user: PropTypes.object,
}

export default injectIntl(withRouter(withStyles(styles)(CustomPaginationActionsTable)))
export default injectIntl(withRouter(withStyles(styles)(CustomPaginationActionsTable)))
12 changes: 12 additions & 0 deletions pmd.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<ruleset name="Custom Rules" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
http://pmd.sourceforge.net/ruleset_2_0_0.xsd">

<!-- Exclude UnnecessaryBlock rule for JavaScript -->
<rule ref="category/ecmascript/codestyle.xml" />
<exclude name="UnnecessaryBlock" />
</rule>

</ruleset>

0 comments on commit 4e1b763

Please sign in to comment.