openreplay/frontend/app/components/Errors/List/List.js
Delirium 3eb9f5cc52
Patch/fix pagination total (#1960)
* fix(ui): fix total pages in pagination

* fix(ui): pages
2024-03-15 12:01:24 +01:00

259 lines
6.8 KiB
JavaScript

import React from 'react';
import { connect } from 'react-redux';
import { Set } from "immutable";
import { NoContent, Loader, Checkbox, IconButton, Input, Pagination } from 'UI';
import { merge, resolve, unresolve, ignore, updateCurrentPage, editOptions } from "Duck/errors";
import { applyFilter } from 'Duck/filters';
import { IGNORED, UNRESOLVED } from 'Types/errorInfo';
import Divider from 'Components/Errors/ui/Divider';
import ListItem from './ListItem/ListItem';
import { debounce } from 'App/utils';
import Select from 'Shared/Select';
import EmptyStateSvg from '../../../svg/no-results.svg';
const sortOptionsMap = {
'occurrence-desc': 'Last Occurrence',
'occurrence-desc': 'First Occurrence',
'sessions-asc': 'Sessions Ascending',
'sessions-desc': 'Sessions Descending',
'users-asc': 'Users Ascending',
'users-desc': 'Users Descending',
};
const sortOptions = Object.entries(sortOptionsMap)
.map(([ value, label ]) => ({ value, label }));
@connect(state => ({
loading: state.getIn([ "errors", "loading" ]),
resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) ||
state.getIn(["errors", "unresolve", "loading"]),
ignoreLoading: state.getIn([ "errors", "ignore", "loading" ]),
mergeLoading: state.getIn([ "errors", "merge", "loading" ]),
currentPage: state.getIn(["errors", "currentPage"]),
limit: state.getIn(["errors", "limit"]),
total: state.getIn([ 'errors', 'totalCount' ]),
sort: state.getIn([ 'errors', 'options', 'sort' ]),
order: state.getIn([ 'errors', 'options', 'order' ]),
query: state.getIn([ "errors", "options", "query" ]),
}), {
merge,
resolve,
unresolve,
ignore,
applyFilter,
updateCurrentPage,
editOptions,
})
export default class List extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
checkedAll: false,
checkedIds: Set(),
query: props.query,
}
this.debounceFetch = debounce(this.props.editOptions, 1000);
}
componentDidMount() {
this.props.applyFilter({ });
}
check = ({ errorId }) => {
const { checkedIds } = this.state;
const newCheckedIds = checkedIds.contains(errorId)
? checkedIds.remove(errorId)
: checkedIds.add(errorId);
this.setState({
checkedAll: newCheckedIds.size === this.props.list.size,
checkedIds: newCheckedIds
});
}
checkAll = () => {
if (this.state.checkedAll) {
this.setState({
checkedAll: false,
checkedIds: Set(),
});
} else {
this.setState({
checkedAll: true,
checkedIds: this.props.list.map(({ errorId }) => errorId).toSet(),
});
}
}
resetChecked = () => {
this.setState({
checkedAll: false,
checkedIds: Set(),
});
}
currentCheckedIds() {
return this.state.checkedIds
.intersect(this.props.list.map(({ errorId }) => errorId).toSet());
}
merge = () => {
this.props.merge(currentCheckedIds().toJS()).then(this.resetChecked);
}
applyToAllChecked(f) {
return Promise.all(this.currentCheckedIds().map(f).toJS()).then(this.resetChecked);
}
resolve = () => {
this.applyToAllChecked(this.props.resolve);
}
unresolve = () => {
this.applyToAllChecked(this.props.unresolve);
}
ignore = () => {
this.applyToAllChecked(this.props.ignore);
}
addPage = () => this.props.updateCurrentPage(this.props.currentPage + 1)
writeOption = ({ name, value }) => {
const [ sort, order ] = value.split('-');
if (name === 'sort') {
this.props.editOptions({ sort, order });
}
}
// onQueryChange = ({ target: { value, name } }) => props.edit({ [ name ]: value })
onQueryChange = ({ target: { value, name } }) => {
this.setState({ query: value });
this.debounceFetch({ query: value });
}
render() {
const {
list,
status,
loading,
ignoreLoading,
resolveToggleLoading,
mergeLoading,
currentPage,
total,
sort,
order,
limit,
} = this.props;
const {
checkedAll,
checkedIds,
query,
} = this.state;
const someLoading = loading || ignoreLoading || resolveToggleLoading || mergeLoading;
const currentCheckedIds = this.currentCheckedIds();
return (
<div className="bg-white p-5 border-radius-3 thin-gray-border">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center" style={{ height: "36px" }}>
<Checkbox
className="mr-3"
checked={ checkedAll }
onChange={ this.checkAll }
/>
{ status === UNRESOLVED
? <IconButton
outline
className="mr-3"
label="Resolve"
icon="check"
size="small"
loading={ resolveToggleLoading }
onClick={ this.resolve }
disabled={ someLoading || currentCheckedIds.size === 0}
/>
: <IconButton
outline
className="mr-3"
label="Unresolve"
icon="exclamation-circle"
size="small"
loading={ resolveToggleLoading }
onClick={ this.unresolve }
disabled={ someLoading || currentCheckedIds.size === 0}
/>
}
{ status !== IGNORED &&
<IconButton
outline
className="mr-3"
label="Ignore"
icon="ban"
size="small"
loading={ ignoreLoading }
onClick={ this.ignore }
disabled={ someLoading || currentCheckedIds.size === 0}
/>
}
</div>
<div className="flex items-center ml-6">
<span className="mr-2 color-gray-medium">Sort By</span>
<Select
defaultValue={ `${sort}-${order}` }
name="sort"
plain
options={ sortOptions }
onChange={ this.writeOption }
/>
<Input
style={{ width: '350px'}}
wrapperClassName="ml-3"
placeholder="Filter by name or message"
icon="search"
name="filter"
onChange={ this.onQueryChange }
value={query}
/>
</div>
</div>
<Divider />
<NoContent
title={
<div className="flex flex-col items-center justify-center">
<object style={{ width: "180px"}} type="image/svg+xml" data={EmptyStateSvg} />
<span className="mr-2">No Errors Found!</span>
</div>
}
subtext="Please try to change your search parameters."
// animatedIcon="empty-state"
show={ !loading && list.size === 0}
>
<Loader loading={ loading }>
{ list.map(e =>
<div key={e.errorId} style={{ opacity: e.disabled ? 0.5 : 1}}>
<ListItem
disabled={someLoading || e.disabled}
key={e.errorId}
error={e}
checked={ checkedIds.contains(e.errorId) }
onCheck={ this.check }
/>
<Divider/>
</div>
)}
<div className="w-full flex items-center justify-center mt-4">
<Pagination
page={currentPage}
total={total}
onPageChange={(page) => this.props.updateCurrentPage(page)}
limit={limit}
debounceRequest={500}
/>
</div>
</Loader>
</NoContent>
</div>
);
}
}