Skip to content

Commit 4e1b763

Browse files
authoredFeb 24, 2024··
Allow user sort (#1075)
* Allow user to sort issue #1013 Fix * added pmd file to root * fix sorting issue in task table
1 parent 09d3636 commit 4e1b763

File tree

2 files changed

+158
-31
lines changed

2 files changed

+158
-31
lines changed
 

‎frontend/src/components/task/task-table.js

+146-31
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,121 @@ const styles = theme => ({
157157
},
158158
})
159159

160+
const tableHeaderMetadata = {
161+
"task.table.head.task": { sortable: true, numeric: false, dataBaseKey: "title" },
162+
"task.table.head.status": { sortable: true, numeric: false, dataBaseKey: "status" },
163+
"task.table.head.project": { sortable: true, numeric: false, dataBaseKey: "Project.name" },
164+
"task.table.head.value": { sortable: true, numeric: true, dataBaseKey: "value" },
165+
"task.table.head.labels": { sortable: true, numeric: false, dataBaseKey: "Labels" },
166+
"task.table.head.createdAt": { sortable: true, numeric: false, dataBaseKey: "createdAt" }
167+
}
168+
169+
const getSortingValue = (item, fieldId) => {
170+
171+
const getValue = (item, dataBaseKey) => {
172+
const keys = dataBaseKey.split(".");
173+
return keys.reduce((obj, key) => (obj && obj[key] !== 'undefined') ? obj[key] : undefined, item);
174+
};
175+
176+
const metadata = tableHeaderMetadata[fieldId];
177+
if (!metadata) {
178+
console.error(`No metadata found for fieldId: ${fieldId}`);
179+
return null;
180+
}
181+
182+
const { numeric, dataBaseKey } = metadata;
183+
184+
const value = getValue(item, dataBaseKey);
185+
186+
if (value === undefined) {
187+
console.error(`Failed to get value for fieldId: ${fieldId}`);
188+
return null;
189+
}
190+
191+
if (numeric) {
192+
const parsedValue = parseFloat(value);
193+
if (isNaN(parsedValue)) {
194+
console.error(`Failed to parse numeric value for fieldId: ${fieldId}`);
195+
return null;
196+
}
197+
return parsedValue;
198+
}
199+
return value;
200+
};
201+
202+
const sortData = (data, sortedBy, sortDirection) => {
203+
if (sortDirection === 'none') return data;
204+
205+
return [...data].sort((a, b) => {
206+
let aValue = getSortingValue(a, sortedBy);
207+
let bValue = getSortingValue(b, sortedBy);
208+
209+
// Handle null values
210+
if (aValue === null || bValue === null) {
211+
return (aValue === null ? (sortDirection === 'asc' ? -1 : 1) : (sortDirection === 'asc' ? 1 : -1));
212+
}
213+
214+
// Handle date sorting
215+
if (sortedBy === 'task.table.head.createdAt') {
216+
let aDate = new Date(aValue).getTime();
217+
let bDate = new Date(bValue).getTime();
218+
return (sortDirection === 'asc' ? aDate - bDate : bDate - aDate);
219+
}
220+
221+
// Handle labels array sorting
222+
if (sortedBy === 'task.table.head.labels') {
223+
aValue = aValue.map(label => label.name).join('');
224+
bValue = bValue.map(label => label.name).join('');
225+
}
226+
227+
// Handle string sorting
228+
let comparator = String(aValue).localeCompare(String(bValue), 'en', { numeric: true, sensitivity: 'base', ignorePunctuation: true });
229+
return (sortDirection === 'asc' ? comparator : -comparator);
230+
});
231+
};
232+
160233
class CustomPaginationActionsTable extends React.Component {
161234
constructor(props) {
162235
super(props)
163236

164237
this.state = {
165238
page: 0,
166239
rowsPerPage: 10,
240+
sortedBy: null,
241+
sortDirection: 'asc',
242+
sortedData: this.props.tasks.data
243+
}
244+
}
245+
246+
componentDidUpdate(prevProps) {
247+
if (prevProps.tasks !== this.props.tasks) {
248+
const { sortedBy, sortDirection } = this.state;
249+
const newSortedData = sortData(this.props.tasks.data, sortedBy, sortDirection);
250+
this.setState({
251+
sortedData: newSortedData
252+
});
167253
}
168254
}
169255

256+
handleSort = (fieldId, sortDirection) => {
257+
const newSortedData = sortData(this.props.tasks.data, fieldId, sortDirection);
258+
259+
return {
260+
sortedBy: fieldId,
261+
sortDirection,
262+
sortedData: newSortedData,
263+
};
264+
}
265+
266+
sortHandler = (fieldId) => {
267+
this.setState((prevState) => {
268+
const { sortedBy, sortDirection } = prevState;
269+
const newSortDirection = sortedBy === fieldId ? (sortDirection === 'asc' ? 'desc' : (sortDirection === 'desc' ? 'none' : 'asc')) : 'asc';
270+
return this.handleSort(fieldId, newSortDirection);
271+
});
272+
};
273+
274+
170275
handleChangePage = (event, page) => {
171276
this.setState({ page })
172277
};
@@ -188,49 +293,59 @@ class CustomPaginationActionsTable extends React.Component {
188293

189294
render() {
190295
const { classes, tasks } = this.props
191-
const { rowsPerPage, page } = this.state
192-
const emptyRows = tasks.data.length ? rowsPerPage - Math.min(rowsPerPage, tasks.data.length - page * rowsPerPage) : 0
296+
const { rowsPerPage, page, sortedBy, sortDirection, sortedData } = this.state;
297+
298+
const emptyRows = sortedData.length ? rowsPerPage - Math.min(rowsPerPage, sortedData.length - page * rowsPerPage) : 0
299+
const TableCellWithSortLogic = ({ fieldId, defineMessage, sortHandler }) => {
300+
return (
301+
<TableSortLabel
302+
active={fieldId === sortedBy && sortDirection !== 'none'}
303+
direction={sortDirection}
304+
onClick={
305+
() => {
306+
return sortHandler(fieldId)
307+
}
308+
}
309+
>
310+
<FormattedMessage id={fieldId} defineMessage={defineMessage} />
311+
</TableSortLabel>
312+
)
313+
}
314+
315+
const TableHeadCustom = () => {
316+
console.log()
317+
return (
318+
<TableHead>
319+
<TableRow>
320+
{Object.entries(tableHeaderMetadata).map(([fieldId, metadata]) => (
321+
<TableCell key={fieldId}>
322+
<TableCellWithSortLogic sortHandler={this.sortHandler} fieldId={fieldId} defaultMessage={metadata.dataBaseKey} />
323+
</TableCell>
324+
))}
325+
</TableRow>
326+
</TableHead>
327+
);
328+
};
193329

194330

195331
if (tasks.completed && tasks.data.length === 0) {
196-
<Paper className={classes.root}>
332+
return (<Paper className={classes.root}>
197333
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 200 }}>
198334
<Typography variant='caption'>
199335
<FormattedMessage id='task.table.body.noIssues' defaultMessage='No issues' />
200336
</Typography>
201337
</div>
202-
</Paper>
338+
</Paper>);
203339
}
204340

205341
return (
206342
<Paper className={classes.root}>
207343
<ReactPlaceholder style={{ marginBottom: 20, padding: 20 }} showLoadingAnimation type='text' rows={12} ready={tasks.completed}>
208344
<div className={classes.tableWrapper}>
209345
<Table className={classes.table}>
210-
<TableHead>
211-
<TableRow>
212-
<TableCell>
213-
<FormattedMessage id='task.table.head.task' defaultMessage='Task' />
214-
</TableCell>
215-
<TableCell>
216-
<FormattedMessage id='task.table.head.status' defaultMessage='Status' />
217-
</TableCell>
218-
<TableCell>
219-
<FormattedMessage id='task.table.head.project' defaultMessage='Project' />
220-
</TableCell>
221-
<TableCell>
222-
<FormattedMessage id='task.table.head.value' defaultMessage='Value' />
223-
</TableCell>
224-
<TableCell>
225-
<FormattedMessage id='task.table.head.labels' defaultMessage='Labels' />
226-
</TableCell>
227-
<TableCell>
228-
<FormattedMessage id='task.table.head.createdAt' defaultMessage='Created' />
229-
</TableCell>
230-
</TableRow>
231-
</TableHead>
346+
<TableHeadCustom />
232347
<TableBody>
233-
{tasks.data.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(n => {
348+
{sortedData.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(n => {
234349
const assigned = n.Assigns.find(a => a.id === n.assigned)
235350
const assignedUser = assigned && assigned.User
236351
return (
@@ -298,11 +413,11 @@ class CustomPaginationActionsTable extends React.Component {
298413
<TableRow>
299414
<TablePagination
300415
colSpan={3}
301-
count={tasks.data.length}
416+
count={sortedData.length}
302417
rowsPerPage={rowsPerPage}
303418
page={page}
304-
onChangePage={(e, page) => this.handleChangePage(e, page)}
305-
onChangeRowsPerPage={(e, page) => this.handleChangeRowsPerPage(e, page)}
419+
onPageChange={(e, page) => this.handleChangePage(e, page)}
420+
onRowsPerPageChange={(e, page) => this.handleChangeRowsPerPage(e, page)}
306421
Actions={TablePaginationActionsWrapped}
307422
/>
308423
</TableRow>
@@ -322,4 +437,4 @@ CustomPaginationActionsTable.propTypes = {
322437
user: PropTypes.object,
323438
}
324439

325-
export default injectIntl(withRouter(withStyles(styles)(CustomPaginationActionsTable)))
440+
export default injectIntl(withRouter(withStyles(styles)(CustomPaginationActionsTable)))

‎pmd.xml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<ruleset name="Custom Rules" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
5+
http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
6+
7+
<!-- Exclude UnnecessaryBlock rule for JavaScript -->
8+
<rule ref="category/ecmascript/codestyle.xml" />
9+
<exclude name="UnnecessaryBlock" />
10+
</rule>
11+
12+
</ruleset>

0 commit comments

Comments
 (0)