Skip to content

Commit 13b9e1e

Browse files
committed
components: add contrib user search list display
* components: add bulk action handlers
1 parent 176c636 commit 13b9e1e

File tree

12 files changed

+328
-1
lines changed

12 files changed

+328
-1
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* This file is part of Invenio.
3+
* Copyright (C) 2022 CERN.
4+
*
5+
* Invenio is free software; you can redistribute it and/or modify it
6+
* under the terms of the MIT License; see LICENSE file for more details.
7+
*/
8+
9+
import React, { Component } from "react";
10+
import PropTypes from "prop-types";
11+
import { Checkbox, Dropdown } from "semantic-ui-react";
12+
import { BulkActionsContext } from "./context";
13+
import _pickBy from "lodash/pickBy";
14+
15+
export default class SearchResultsBulkActions extends Component {
16+
constructor(props) {
17+
super(props);
18+
const { allSelected } = this.props;
19+
this.state = { allSelectedChecked: allSelected };
20+
}
21+
22+
componentDidMount() {
23+
const { allSelected } = this.context;
24+
// eslint-disable-next-line react/no-did-mount-set-state
25+
this.setState({ allSelectedChecked: allSelected });
26+
}
27+
28+
static contextType = BulkActionsContext;
29+
30+
handleOnChange = () => {
31+
const { setAllSelected, allSelected } = this.context;
32+
this.setState({ allSelectedChecked: !allSelected });
33+
setAllSelected(!allSelected, true);
34+
};
35+
36+
handleActionOnChange = (e, { value, ...props }) => {
37+
if (!value) return;
38+
39+
const { optionSelectionCallback } = this.props;
40+
41+
const { selectedCount, bulkActionContext } = this.context;
42+
const selected = _pickBy(bulkActionContext, ({ selected }) => selected === true);
43+
optionSelectionCallback(value, selected, selectedCount);
44+
};
45+
46+
render() {
47+
const { bulkDropdownOptions } = this.props;
48+
const { allSelectedChecked } = this.state;
49+
const { allSelected, selectedCount } = this.context;
50+
51+
const noneSelected = selectedCount === 0;
52+
53+
const dropdownOptions = bulkDropdownOptions.map(({ key, value, text }) => ({
54+
key: key,
55+
value: value,
56+
text: text,
57+
disabled: noneSelected,
58+
}));
59+
60+
return (
61+
<div className="flex">
62+
<Checkbox
63+
className="align-self-center mr-10"
64+
onChange={this.handleOnChange}
65+
checked={allSelectedChecked && allSelected}
66+
/>
67+
<Dropdown
68+
className="align-self-center fluid-responsive"
69+
text={`${selectedCount} members selected`}
70+
options={dropdownOptions}
71+
aria-label="bulk actions"
72+
item
73+
selection
74+
value={null}
75+
selectOnBlur={false}
76+
onChange={this.handleActionOnChange}
77+
selectOnNavigation={false}
78+
/>
79+
</div>
80+
);
81+
}
82+
}
83+
84+
SearchResultsBulkActions.propTypes = {
85+
bulkDropdownOptions: PropTypes.array.isRequired,
86+
allSelected: PropTypes.bool,
87+
optionSelectionCallback: PropTypes.func.isRequired,
88+
};
89+
90+
SearchResultsBulkActions.defaultProps = {
91+
allSelected: false,
92+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { BulkActionsContext } from "./context";
2+
import React, { Component } from "react";
3+
import _hasIn from "lodash/hasIn";
4+
import PropTypes from "prop-types";
5+
6+
export default class SearchResultsBulkActionsManager extends Component {
7+
constructor(props) {
8+
super(props);
9+
10+
this.selected = {};
11+
this.state = { allSelected: false, selectedCount: 0 };
12+
}
13+
14+
addToSelected = (rowId, data) => {
15+
const { selectedCount } = this.state;
16+
if (_hasIn(this.selected, `${rowId}`)) {
17+
this.selected[rowId].selected = !this.selected[rowId].selected;
18+
} else {
19+
this.selected[rowId].selected = true;
20+
this.selected[rowId].data = data;
21+
}
22+
23+
if (!this.selected[rowId].selected) {
24+
this.setAllSelected(false);
25+
this.setSelectedCount(selectedCount - 1);
26+
} else {
27+
const updatedCount = selectedCount + 1;
28+
this.setSelectedCount(updatedCount);
29+
if (Object.keys(this.selected).length === updatedCount) {
30+
this.setAllSelected(true);
31+
}
32+
}
33+
};
34+
35+
setSelectedCount = (count) => {
36+
this.setState({ selectedCount: count });
37+
};
38+
39+
setAllSelected = (val, global = false) => {
40+
this.setState({ allSelected: val });
41+
if (global) {
42+
for (const [key] of Object.entries(this.selected)) {
43+
this.selected[key].selected = val;
44+
}
45+
if (val) {
46+
this.setSelectedCount(Object.keys(this.selected).length);
47+
} else {
48+
this.setSelectedCount(0);
49+
}
50+
}
51+
};
52+
53+
render() {
54+
const { children } = this.props;
55+
const { allSelected, selectedCount } = this.state;
56+
return (
57+
<BulkActionsContext.Provider
58+
value={{
59+
bulkActionContext: this.selected,
60+
addToSelected: this.addToSelected,
61+
setAllSelected: this.setAllSelected,
62+
allSelected: allSelected,
63+
selectedCount: selectedCount,
64+
}}
65+
>
66+
{children}
67+
</BulkActionsContext.Provider>
68+
);
69+
}
70+
}
71+
72+
SearchResultsBulkActionsManager.contextType = BulkActionsContext;
73+
74+
SearchResultsBulkActionsManager.propTypes = {
75+
children: PropTypes.node.isRequired,
76+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { BulkActionsContext } from "./context";
2+
import React, { Component } from "react";
3+
import PropTypes from "prop-types";
4+
import { Checkbox } from "semantic-ui-react";
5+
import _hasIn from "lodash/hasIn";
6+
7+
export class SearchResultsRowCheckbox extends Component {
8+
constructor(props) {
9+
super(props);
10+
this.state = { isChecked: false };
11+
}
12+
13+
componentDidMount() {
14+
this.subscribeToContext();
15+
const { bulkActionContext, allSelected } = this.context;
16+
// eslint-disable-next-line react/no-did-mount-set-state
17+
this.setState({
18+
isChecked: this.isChecked(bulkActionContext, allSelected),
19+
});
20+
}
21+
22+
static contextType = BulkActionsContext;
23+
24+
isChecked = (bulkActionContext, allSelected) => {
25+
const { rowId } = this.props;
26+
if (_hasIn(bulkActionContext, `${rowId}`) || allSelected) {
27+
return bulkActionContext[rowId].selected;
28+
}
29+
return false;
30+
};
31+
32+
subscribeToContext = () => {
33+
const { rowId, data } = this.props;
34+
const { allSelected, bulkActionContext } = this.context;
35+
if (!_hasIn(bulkActionContext, `${rowId}`)) {
36+
bulkActionContext[rowId] = { selected: allSelected, data: data };
37+
}
38+
};
39+
40+
handleOnChange = () => {
41+
const { addToSelected } = this.context;
42+
const { rowId, data } = this.props;
43+
const { isChecked } = this.state;
44+
this.setState({ isChecked: !isChecked });
45+
addToSelected(rowId, data);
46+
};
47+
48+
render() {
49+
const { bulkActionContext, allSelected } = this.context;
50+
return (
51+
<Checkbox
52+
className="mt-auto mb-auto "
53+
checked={this.isChecked(bulkActionContext, allSelected) || allSelected}
54+
onChange={this.handleOnChange}
55+
/>
56+
);
57+
}
58+
}
59+
60+
SearchResultsRowCheckbox.propTypes = {
61+
rowId: PropTypes.string.isRequired,
62+
data: PropTypes.object.isRequired,
63+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from "react";
2+
3+
export const BulkActionsContext = React.createContext({
4+
bulkActionContext: {},
5+
addToSelected: () => {},
6+
allSelected: false,
7+
setAllSelected: () => {},
8+
selectedCount: 0,
9+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { BulkActionsContext } from "./context";
2+
export { default as SearchResultsBulkActions } from "./SearchResultsBulkActions";
3+
export { default as SearchResultsBulkActionsManager } from "./SearchResultsBulkActionsManager";
4+
export { SearchResultsRowCheckbox } from "./SearchResultsRowCheckbox";

src/lib/elements/contrib/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* // This file is part of invenio-app-rdm
3+
* // Copyright (C) 2023 CERN.
4+
* //
5+
* // invenio-app-rdm is free software; you can redistribute it and/or modify it
6+
* // under the terms of the MIT License; see LICENSE file for more details.
7+
*/
8+
9+
export * from "./invenioRDM";
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* // This file is part of React-Invenio-Forms
3+
* // Copyright (C) 2023 CERN.
4+
* //
5+
* // React-Invenio-Forms is free software; you can redistribute it and/or modify it
6+
* // under the terms of the MIT License; see LICENSE file for more details.
7+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* // This file is part of React-Invenio-Forms
3+
* // Copyright (C) 2023 CERN.
4+
* //
5+
* // React-Invenio-Forms is free software; you can redistribute it and/or modify it
6+
* // under the terms of the MIT License; see LICENSE file for more details.
7+
*/
8+
9+
export * from "./users";
10+
export * from "./groups";
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { Component } from "react";
2+
import PropTypes from "prop-types";
3+
import { Image } from "../../../Image";
4+
import { Item, Label } from "semantic-ui-react";
5+
6+
export class UserListItemCompact extends Component {
7+
render() {
8+
const { id, user, linkToDetailView } = this.props;
9+
const name = user.profile.full_name || user.profile.email || user.profile.username;
10+
return (
11+
<Item className="flex" key={id}>
12+
<Image src={user.links.avatar} avatar loadFallbackFirst />
13+
<Item.Content className="ml-10">
14+
<Item.Header className={!user.description ? "mt-5" : ""}>
15+
{linkToDetailView ? (
16+
<a href={linkToDetailView}>
17+
<b>{name}</b>
18+
</a>
19+
) : (
20+
<b>{name}</b>
21+
)}
22+
{user.type === "group" && <Label className="ml-10">Group</Label>}
23+
{user.is_current_user && (
24+
<Label size="tiny" className="primary ml-10">
25+
You
26+
</Label>
27+
)}
28+
</Item.Header>
29+
<Item.Meta>
30+
<div className="truncate-lines-1"> {user.profile.affiliations}</div>
31+
</Item.Meta>
32+
</Item.Content>
33+
</Item>
34+
);
35+
}
36+
}
37+
38+
UserListItemCompact.propTypes = {
39+
user: PropTypes.object.isRequired,
40+
id: PropTypes.string.isRequired,
41+
linkToDetailView: PropTypes.string,
42+
};
43+
44+
UserListItemCompact.defaultProps = {
45+
linkToDetailView: undefined,
46+
};

0 commit comments

Comments
 (0)