Skip to content

Commit

Permalink
components: add contrib user search list display
Browse files Browse the repository at this point in the history
* components: add bulk action handlers
  • Loading branch information
kpsherva committed Sep 8, 2023
1 parent 176c636 commit 6560693
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions src/lib/elements/bulk_actions/SearchResultsBulkActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* This file is part of Invenio.
* Copyright (C) 2022 CERN.
*
* Invenio is free software; you can redistribute it and/or modify it
* under the terms of the MIT License; see LICENSE file for more details.
*/

import React, { Component } from "react";
import PropTypes from "prop-types";
import { Checkbox, Dropdown } from "semantic-ui-react";
import { BulkActionsContext } from "./context";
import _pickBy from "lodash/pickBy";

export class SearchResultsBulkActions extends Component {
constructor(props) {
super(props);
const { allSelected } = this.props;
this.state = { allSelectedChecked: allSelected };
}

componentDidMount() {
const { allSelected } = this.context;
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ allSelectedChecked: allSelected });
}

static contextType = BulkActionsContext;

handleOnChange = () => {
const { setAllSelected, allSelected } = this.context;
this.setState({ allSelectedChecked: !allSelected });
setAllSelected(!allSelected, true);
};

handleActionOnChange = (e, { value, ...props }) => {
if (!value) return;

const { optionSelectionCallback } = this.props;

const { selectedCount, bulkActionContext } = this.context;
const selected = _pickBy(bulkActionContext, ({ selected }) => selected === true);
optionSelectionCallback(value, selected, selectedCount);
};

render() {
const { bulkDropdownOptions } = this.props;
const { allSelectedChecked } = this.state;
const { allSelected, selectedCount } = this.context;

const noneSelected = selectedCount === 0;

const dropdownOptions = bulkDropdownOptions.map(({ key, value, text }) => ({
key: key,
value: value,
text: text,
disabled: noneSelected,
}));

return (
<div className="flex">
<Checkbox
className="align-self-center mr-10"
onChange={this.handleOnChange}
checked={allSelectedChecked && allSelected}
/>
<Dropdown
className="align-self-center fluid-responsive"
text={`${selectedCount} members selected`}
options={dropdownOptions}
aria-label="bulk actions"
item
selection
value={null}
selectOnBlur={false}
onChange={this.handleActionOnChange}
selectOnNavigation={false}
/>
</div>
);
}
}

SearchResultsBulkActions.propTypes = {
bulkDropdownOptions: PropTypes.array.isRequired,
allSelected: PropTypes.bool,
optionSelectionCallback: PropTypes.func.isRequired,
};

SearchResultsBulkActions.defaultProps = {
allSelected: false,
};

export default Overridable.component(
"SearchResultsBulkActions",
SearchResultsBulkActions
);
81 changes: 81 additions & 0 deletions src/lib/elements/bulk_actions/SearchResultsBulkActionsManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { BulkActionsContext } from "./context";
import React, { Component } from "react";
import _hasIn from "lodash/hasIn";
import PropTypes from "prop-types";

class SearchResultsBulkActionsManager extends Component {
constructor(props) {
super(props);

this.selected = {};
this.state = { allSelected: false, selectedCount: 0 };
}

addToSelected = (rowId, data) => {
const { selectedCount } = this.state;
if (_hasIn(this.selected, `${rowId}`)) {
this.selected[rowId].selected = !this.selected[rowId].selected;
} else {
this.selected[rowId].selected = true;
this.selected[rowId].data = data;
}

if (!this.selected[rowId].selected) {
this.setAllSelected(false);
this.setSelectedCount(selectedCount - 1);
} else {
const updatedCount = selectedCount + 1;
this.setSelectedCount(updatedCount);
if (Object.keys(this.selected).length === updatedCount) {
this.setAllSelected(true);
}
}
};

setSelectedCount = (count) => {
this.setState({ selectedCount: count });
};

setAllSelected = (val, global = false) => {
this.setState({ allSelected: val });
if (global) {
for (const [key] of Object.entries(this.selected)) {
this.selected[key].selected = val;
}
if (val) {
this.setSelectedCount(Object.keys(this.selected).length);
} else {
this.setSelectedCount(0);
}
}
};

render() {
const { children } = this.props;
const { allSelected, selectedCount } = this.state;
return (
<BulkActionsContext.Provider
value={{
bulkActionContext: this.selected,
addToSelected: this.addToSelected,
setAllSelected: this.setAllSelected,
allSelected: allSelected,
selectedCount: selectedCount,
}}
>
{children}
</BulkActionsContext.Provider>
);
}
}

SearchResultsBulkActionsManager.contextType = BulkActionsContext;

SearchResultsBulkActionsManager.propTypes = {
children: PropTypes.node.isRequired,
};

export default Overridable.component(
"SearchResultsBulkActionsManager",
SearchResultsBulkActionsManager
);
63 changes: 63 additions & 0 deletions src/lib/elements/bulk_actions/SearchResultsRowCheckbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { BulkActionsContext } from "./context";
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Checkbox } from "semantic-ui-react";
import _hasIn from "lodash/hasIn";

export class SearchResultsRowCheckbox extends Component {
constructor(props) {
super(props);
this.state = { isChecked: false };
}

componentDidMount() {
this.subscribeToContext();
const { bulkActionContext, allSelected } = this.context;
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({
isChecked: this.isChecked(bulkActionContext, allSelected),
});
}

static contextType = BulkActionsContext;

isChecked = (bulkActionContext, allSelected) => {
const { rowId } = this.props;
if (_hasIn(bulkActionContext, `${rowId}`) || allSelected) {
return bulkActionContext[rowId].selected;
}
return false;
};

subscribeToContext = () => {
const { rowId, data } = this.props;
const { allSelected, bulkActionContext } = this.context;
if (!_hasIn(bulkActionContext, `${rowId}`)) {
bulkActionContext[rowId] = { selected: allSelected, data: data };
}
};

handleOnChange = () => {
const { addToSelected } = this.context;
const { rowId, data } = this.props;
const { isChecked } = this.state;
this.setState({ isChecked: !isChecked });
addToSelected(rowId, data);
};

render() {
const { bulkActionContext, allSelected } = this.context;
return (
<Checkbox
className="mt-auto mb-auto "
checked={this.isChecked(bulkActionContext, allSelected) || allSelected}
onChange={this.handleOnChange}
/>
);
}
}

SearchResultsRowCheckbox.propTypes = {
rowId: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
};
9 changes: 9 additions & 0 deletions src/lib/elements/bulk_actions/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

export const BulkActionsContext = React.createContext({
bulkActionContext: {},
addToSelected: () => {},
allSelected: false,
setAllSelected: () => {},
selectedCount: 0,
});
4 changes: 4 additions & 0 deletions src/lib/elements/bulk_actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { BulkActionsContext } from "./context";
export { SearchResultsBulkActions } from "./SearchResultsBulkActions";
export { default as SearchResultsBulkActionsManager } from "./SearchResultsBulkActionsManager";
export { SearchResultsRowCheckbox } from "./SearchResultsRowCheckbox";
9 changes: 9 additions & 0 deletions src/lib/elements/contrib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* // This file is part of invenio-app-rdm
* // Copyright (C) 2023 CERN.
* //
* // invenio-app-rdm is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/

export * from "./invenioRDM";
7 changes: 7 additions & 0 deletions src/lib/elements/contrib/invenioRDM/groups/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* // This file is part of React-Invenio-Forms
* // Copyright (C) 2023 CERN.
* //
* // React-Invenio-Forms is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/
10 changes: 10 additions & 0 deletions src/lib/elements/contrib/invenioRDM/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* // This file is part of React-Invenio-Forms
* // Copyright (C) 2023 CERN.
* //
* // React-Invenio-Forms is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/

export * from "./users";
export * from "./groups";
46 changes: 46 additions & 0 deletions src/lib/elements/contrib/invenioRDM/users/UserListItemCompact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Image } from "../../../Image";
import { Item, Label } from "semantic-ui-react";

export class UserListItemCompact extends Component {
render() {
const { id, user, linkToDetailView } = this.props;
const name = user.profile.full_name || user.profile.email || user.profile.username;
return (
<Item className="flex" key={id}>
<Image src={user.links.avatar} avatar loadFallbackFirst />
<Item.Content className="ml-10">
<Item.Header className={!user.description ? "mt-5" : ""}>
{linkToDetailView ? (
<a href={linkToDetailView}>
<b>{name}</b>
</a>
) : (
<b>{name}</b>
)}
{user.type === "group" && <Label className="ml-10">Group</Label>}
{user.is_current_user && (
<Label size="tiny" className="primary ml-10">
You
</Label>
)}
</Item.Header>
<Item.Meta>
<div className="truncate-lines-1"> {user.profile.affiliations}</div>
</Item.Meta>
</Item.Content>
</Item>
);
}
}

UserListItemCompact.propTypes = {
user: PropTypes.object.isRequired,
id: PropTypes.string.isRequired,
linkToDetailView: PropTypes.string,
};

UserListItemCompact.defaultProps = {
linkToDetailView: undefined,
};
9 changes: 9 additions & 0 deletions src/lib/elements/contrib/invenioRDM/users/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* // This file is part of React-Invenio-Forms
* // Copyright (C) 2023 CERN.
* //
* // React-Invenio-Forms is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/

export { UserListItemCompact } from "./UserListItemCompact";
2 changes: 2 additions & 0 deletions src/lib/elements/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* This folder contains general purpose reusable components.
*/
export * from "./accessibility";
export * from "./contrib";
export * from "./bulk_actions";
export { Image } from "./Image";
export { GridResponsiveSidebarColumn } from "./GridResponsiveSidebarColumn";
export { ErrorMessage } from "./ErrorMessage";

0 comments on commit 6560693

Please sign in to comment.