mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02:30
flushing out new approach to MultiCredentialsLookup
This commit is contained in:
@@ -3,32 +3,21 @@ import { arrayOf, string, func, object } from 'prop-types';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
import { FormGroup } from '@patternfly/react-core';
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { InstanceGroupsAPI } from '@api';
|
import { InstanceGroupsAPI } from '@api';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
import { FieldTooltip } from '@components/FormField';
|
||||||
import Lookup from './NewLookup';
|
import Lookup from './NewLookup';
|
||||||
|
import SelectList from './shared/SelectList';
|
||||||
|
|
||||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
const QS_CONFIG = getQSConfig('instance_groups', {
|
||||||
margin-left: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('instance-groups', {
|
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 5,
|
page_size: 5,
|
||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
});
|
});
|
||||||
// const getInstanceGroups = async params => InstanceGroupsAPI.read(params);
|
|
||||||
|
|
||||||
function InstanceGroupsLookup({
|
function InstanceGroupsLookup(props) {
|
||||||
value,
|
const { value, onChange, tooltip, className, history, i18n } = props;
|
||||||
onChange,
|
|
||||||
tooltip,
|
|
||||||
className,
|
|
||||||
history,
|
|
||||||
i18n,
|
|
||||||
}) {
|
|
||||||
const [instanceGroups, setInstanceGroups] = useState([]);
|
const [instanceGroups, setInstanceGroups] = useState([]);
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@@ -46,56 +35,62 @@ function InstanceGroupsLookup({
|
|||||||
})();
|
})();
|
||||||
}, [history.location]);
|
}, [history.location]);
|
||||||
|
|
||||||
/*
|
|
||||||
Wrapping <div> added to workaround PF bug:
|
|
||||||
https://github.com/patternfly/patternfly-react/issues/2855
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<FormGroup
|
||||||
<FormGroup
|
className={className}
|
||||||
label={i18n._(t`Instance Groups`)}
|
label={i18n._(t`Instance Groups`)}
|
||||||
fieldId="org-instance-groups"
|
fieldId="org-instance-groups"
|
||||||
>
|
>
|
||||||
{tooltip && (
|
{tooltip && <FieldTooltip content={tooltip} />}
|
||||||
<Tooltip position="right" content={tooltip}>
|
<Lookup
|
||||||
<QuestionCircleIcon />
|
id="org-instance-groups"
|
||||||
</Tooltip>
|
header={i18n._(t`Instance Groups`)}
|
||||||
|
// name="instanceGroups"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
// items={instanceGroups}
|
||||||
|
// count={count}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
multiple
|
||||||
|
// columns={}
|
||||||
|
sortedColumnKey="name"
|
||||||
|
renderSelectList={({ state, dispatch, canDelete }) => (
|
||||||
|
<SelectList
|
||||||
|
value={state.selectedItems}
|
||||||
|
options={instanceGroups}
|
||||||
|
optionCount={count}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified`),
|
||||||
|
key: 'modified',
|
||||||
|
isSortable: false,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created`),
|
||||||
|
key: 'created',
|
||||||
|
isSortable: false,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
multiple={state.multiple}
|
||||||
|
header={i18n._(t`Instance Groups`)}
|
||||||
|
name="instanceGroups"
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
readOnly={!canDelete}
|
||||||
|
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
|
||||||
|
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Lookup
|
/>
|
||||||
id="org-instance-groups"
|
{error ? <div>error {error.message}</div> : ''}
|
||||||
lookupHeader={i18n._(t`Instance Groups`)}
|
</FormGroup>
|
||||||
name="instanceGroups"
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
items={instanceGroups}
|
|
||||||
count={count}
|
|
||||||
qsConfig={QS_CONFIG}
|
|
||||||
multiple
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
name: i18n._(t`Name`),
|
|
||||||
key: 'name',
|
|
||||||
isSortable: true,
|
|
||||||
isSearchable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18n._(t`Modified`),
|
|
||||||
key: 'modified',
|
|
||||||
isSortable: false,
|
|
||||||
isNumeric: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18n._(t`Created`),
|
|
||||||
key: 'created',
|
|
||||||
isSortable: false,
|
|
||||||
isNumeric: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
sortedColumnKey="name"
|
|
||||||
/>
|
|
||||||
{error ? <div>error {error.message}</div> : ''}
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
import { FormGroup, ToolbarItem } from '@patternfly/react-core';
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
import CategoryLookup from '@components/Lookup/CategoryLookup';
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
|
import { FieldTooltip } from '@components/FormField';
|
||||||
|
import { CredentialChip } from '@components/Chip';
|
||||||
|
import VerticalSeperator from '@components/VerticalSeparator';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
import Lookup from './NewLookup';
|
||||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
import SelectList from './shared/SelectList';
|
||||||
margin-left: 10px;
|
import multiCredentialReducer from './shared/multiCredentialReducer';
|
||||||
`;
|
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('credentials', {
|
const QS_CONFIG = getQSConfig('credentials', {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -20,6 +20,7 @@ const QS_CONFIG = getQSConfig('credentials', {
|
|||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: move into reducer
|
||||||
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
||||||
let newCredentialsList;
|
let newCredentialsList;
|
||||||
const isSelectedCredentialInState =
|
const isSelectedCredentialInState =
|
||||||
@@ -39,124 +40,164 @@ function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
|||||||
return newCredentialsList;
|
return newCredentialsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultiCredentialsLookup extends React.Component {
|
async function loadCredentialTypes() {
|
||||||
constructor(props) {
|
const { data } = await CredentialTypesAPI.read();
|
||||||
super(props);
|
const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
|
||||||
|
const credentialTypes = [];
|
||||||
this.state = {
|
// TODO: cleanup
|
||||||
selectedCredentialType: { label: 'Machine', id: 1, kind: 'ssh' },
|
data.results.forEach(cred => {
|
||||||
credentialTypes: [],
|
acceptableTypes.forEach(aT => {
|
||||||
};
|
if (aT === cred.kind) {
|
||||||
this.loadCredentialTypes = this.loadCredentialTypes.bind(this);
|
// This object has several repeated values as some of it's children
|
||||||
this.handleCredentialTypeSelect = this.handleCredentialTypeSelect.bind(
|
// require different field values.
|
||||||
this
|
cred = {
|
||||||
);
|
id: cred.id,
|
||||||
this.loadCredentials = this.loadCredentials.bind(this);
|
key: cred.id,
|
||||||
}
|
kind: cred.kind,
|
||||||
|
type: cred.namespace,
|
||||||
componentDidMount() {
|
value: cred.name,
|
||||||
this.loadCredentialTypes();
|
label: cred.name,
|
||||||
this.loadCredentials();
|
isDisabled: false,
|
||||||
}
|
};
|
||||||
|
credentialTypes.push(cred);
|
||||||
async loadCredentialTypes() {
|
}
|
||||||
const { onError } = this.props;
|
|
||||||
try {
|
|
||||||
const { data } = await CredentialTypesAPI.read();
|
|
||||||
const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
|
|
||||||
const credentialTypes = [];
|
|
||||||
data.results.forEach(cred => {
|
|
||||||
acceptableTypes.forEach(aT => {
|
|
||||||
if (aT === cred.kind) {
|
|
||||||
// This object has several repeated values as some of it's children
|
|
||||||
// require different field values.
|
|
||||||
cred = {
|
|
||||||
id: cred.id,
|
|
||||||
key: cred.id,
|
|
||||||
kind: cred.kind,
|
|
||||||
type: cred.namespace,
|
|
||||||
value: cred.name,
|
|
||||||
label: cred.name,
|
|
||||||
isDisabled: false,
|
|
||||||
};
|
|
||||||
credentialTypes.push(cred);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.setState({ credentialTypes });
|
|
||||||
} catch (err) {
|
|
||||||
onError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadCredentials() {
|
|
||||||
const { history, onError } = this.props;
|
|
||||||
const { selectedCredentialType } = this.state;
|
|
||||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
|
||||||
params.credential_type = selectedCredentialType.id || 1;
|
|
||||||
try {
|
|
||||||
const { data } = await CredentialsAPI.read(params);
|
|
||||||
this.setState({
|
|
||||||
credentials: data.results,
|
|
||||||
count: data.count,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
onError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCredentialTypeSelect(value, type) {
|
|
||||||
const { credentialTypes } = this.state;
|
|
||||||
const selectedType = credentialTypes.filter(item => item.label === type);
|
|
||||||
this.setState({ selectedCredentialType: selectedType[0] }, () => {
|
|
||||||
this.loadCredentials();
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
return credentialTypes;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
async function loadCredentials(params, selectedCredentialTypeId) {
|
||||||
const {
|
params.credential_type = selectedCredentialTypeId || 1;
|
||||||
selectedCredentialType,
|
const { data } = await CredentialsAPI.read(params);
|
||||||
credentialTypes,
|
return data;
|
||||||
credentials,
|
}
|
||||||
count,
|
|
||||||
} = this.state;
|
function MultiCredentialsLookup(props) {
|
||||||
const { tooltip, i18n, value, onChange } = this.props;
|
const { history, tooltip, value, onChange, onError, i18n } = props;
|
||||||
return (
|
const [credentialTypes, setCredentialTypes] = useState([]);
|
||||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
const [selectedType, setSelectedType] = useState(null);
|
||||||
{tooltip && (
|
const [credentials, setCredentials] = useState([]);
|
||||||
<Tooltip position="right" content={tooltip}>
|
const [credentialsCount, setCredentialsCount] = useState(0);
|
||||||
<QuestionCircleIcon />
|
|
||||||
</Tooltip>
|
useEffect(() => {
|
||||||
)}
|
(async () => {
|
||||||
{credentialTypes && (
|
try {
|
||||||
<CategoryLookup
|
const types = await loadCredentialTypes();
|
||||||
selectCategoryOptions={credentialTypes}
|
setCredentialTypes(types);
|
||||||
selectCategory={this.handleCredentialTypeSelect}
|
setSelectedType(types[0]);
|
||||||
selectedCategory={selectedCredentialType}
|
} catch (err) {
|
||||||
onToggleItem={toggleCredentialSelection}
|
onError(err);
|
||||||
onloadCategories={this.loadCredentialTypes}
|
}
|
||||||
id="multiCredential"
|
})();
|
||||||
lookupHeader={i18n._(t`Credentials`)}
|
}, []);
|
||||||
name="credentials"
|
|
||||||
value={value}
|
useEffect(() => {
|
||||||
multiple
|
console.log('useEffect', selectedType);
|
||||||
onChange={onChange}
|
(async () => {
|
||||||
items={credentials}
|
if (!selectedType) {
|
||||||
count={count}
|
return;
|
||||||
qsConfig={QS_CONFIG}
|
}
|
||||||
columns={[
|
try {
|
||||||
{
|
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||||
name: i18n._(t`Name`),
|
const { results, count } = await loadCredentials(
|
||||||
key: 'name',
|
params,
|
||||||
isSortable: true,
|
selectedType.id
|
||||||
isSearchable: true,
|
);
|
||||||
},
|
setCredentials(results);
|
||||||
]}
|
setCredentialsCount(count);
|
||||||
|
} catch (err) {
|
||||||
|
onError(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [selectedType]);
|
||||||
|
|
||||||
|
// handleCredentialTypeSelect(value, type) {
|
||||||
|
// const { credentialTypes } = this.state;
|
||||||
|
// const selectedType = credentialTypes.filter(item => item.label === type);
|
||||||
|
// this.setState({ selectedCredentialType: selectedType[0] }, () => {
|
||||||
|
// this.loadCredentials();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// selectedCredentialType,
|
||||||
|
// credentialTypes,
|
||||||
|
// credentials,
|
||||||
|
// credentialsCount,
|
||||||
|
// } = state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
||||||
|
{tooltip && <FieldTooltip content={tooltip} />}
|
||||||
|
<Lookup
|
||||||
|
reducer={multiCredentialReducer}
|
||||||
|
onToggleItem={toggleCredentialSelection}
|
||||||
|
id="multiCredential"
|
||||||
|
lookupHeader={i18n._(t`Credentials`)}
|
||||||
|
// name="credentials"
|
||||||
|
value={value}
|
||||||
|
multiple
|
||||||
|
onChange={onChange}
|
||||||
|
// items={credentials}
|
||||||
|
// count={credentialsCount}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
// columns={}
|
||||||
|
// TODO bind removeItem
|
||||||
|
renderItemChip={({ item, removeItem, canDelete }) => (
|
||||||
|
<CredentialChip
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => removeItem(item)}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
credential={item}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FormGroup>
|
renderSelectList={({ state, dispatch, canDelete }) => {
|
||||||
);
|
return (
|
||||||
}
|
<>
|
||||||
|
{credentialTypes && credentialTypes.length > 0 && (
|
||||||
|
<ToolbarItem css=" display: flex; align-items: center;">
|
||||||
|
<div css="flex: 0 0 25%;">{i18n._(t`Selected Category`)}</div>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<AnsibleSelect
|
||||||
|
css="flex: 1 1 75%;"
|
||||||
|
id="multiCredentialsLookUp-select"
|
||||||
|
label={i18n._(t`Selected Category`)}
|
||||||
|
data={credentialTypes}
|
||||||
|
value={selectedType && selectedType.label}
|
||||||
|
onChange={(e, label) => {
|
||||||
|
setSelectedType(
|
||||||
|
credentialTypes.find(o => o.label === label)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ToolbarItem>
|
||||||
|
)}
|
||||||
|
<SelectList
|
||||||
|
value={state.selectedItems}
|
||||||
|
options={credentials}
|
||||||
|
optionCount={credentialsCount}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
multiple={selectedType && selectedType.value === 'Vault'}
|
||||||
|
header={i18n._(t`Credentials`)}
|
||||||
|
name="credentials"
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
readOnly={!canDelete}
|
||||||
|
selectItem={() => {}}
|
||||||
|
deselectItem={() => {}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiCredentialsLookup.propTypes = {
|
MultiCredentialsLookup.propTypes = {
|
||||||
@@ -178,6 +219,6 @@ MultiCredentialsLookup.defaultProps = {
|
|||||||
tooltip: '',
|
tooltip: '',
|
||||||
value: [],
|
value: [],
|
||||||
};
|
};
|
||||||
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
|
||||||
|
|
||||||
|
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
||||||
export default withI18n()(withRouter(MultiCredentialsLookup));
|
export default withI18n()(withRouter(MultiCredentialsLookup));
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { t } from '@lingui/macro';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import reducer, { initReducer } from './shared/reducer';
|
import reducer, { initReducer } from './shared/reducer';
|
||||||
import SelectList from './shared/SelectList';
|
|
||||||
import { ChipGroup, Chip } from '../Chip';
|
import { ChipGroup, Chip } from '../Chip';
|
||||||
import { QSConfig } from '@types';
|
import { QSConfig } from '@types';
|
||||||
|
|
||||||
@@ -51,20 +50,28 @@ const ChipHolder = styled.div`
|
|||||||
function Lookup(props) {
|
function Lookup(props) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
items,
|
// items,
|
||||||
count,
|
// count,
|
||||||
header,
|
header,
|
||||||
name,
|
// name,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
columns,
|
// columns,
|
||||||
value,
|
value,
|
||||||
multiple,
|
multiple,
|
||||||
required,
|
required,
|
||||||
qsConfig,
|
qsConfig,
|
||||||
|
renderItemChip,
|
||||||
|
renderSelectList,
|
||||||
|
history,
|
||||||
i18n,
|
i18n,
|
||||||
} = props;
|
} = props;
|
||||||
const [state, dispatch] = useReducer(reducer, props, initReducer);
|
|
||||||
|
const [state, dispatch] = useReducer(
|
||||||
|
reducer,
|
||||||
|
{ value, multiple, required },
|
||||||
|
initReducer
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: 'SET_MULTIPLE', value: multiple });
|
dispatch({ type: 'SET_MULTIPLE', value: multiple });
|
||||||
@@ -74,10 +81,18 @@ function Lookup(props) {
|
|||||||
dispatch({ type: 'SET_VALUE', value });
|
dispatch({ type: 'SET_VALUE', value });
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
const clearQSParams = () => {
|
||||||
|
const parts = history.location.search.replace(/^\?/, '').split('&');
|
||||||
|
const ns = qsConfig.namespace;
|
||||||
|
const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
||||||
|
history.push(`${history.location.pathname}?${otherParts.join('&')}`);
|
||||||
|
};
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
const { selectedItems } = state;
|
const { selectedItems } = state;
|
||||||
const val = multiple ? selectedItems : selectedItems[0] || null;
|
const val = multiple ? selectedItems : selectedItems[0] || null;
|
||||||
onChange(val);
|
onChange(val);
|
||||||
|
clearQSParams();
|
||||||
dispatch({ type: 'CLOSE_MODAL' });
|
dispatch({ type: 'CLOSE_MODAL' });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,8 +104,12 @@ function Lookup(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { isModalOpen, selectedItems } = state;
|
const closeModal = () => {
|
||||||
|
clearQSParams();
|
||||||
|
dispatch({ type: 'CLOSE_MODAL' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isModalOpen, selectedItems } = state;
|
||||||
const canDelete = !required || (multiple && value.length > 1);
|
const canDelete = !required || (multiple && value.length > 1);
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@@ -105,15 +124,13 @@ function Lookup(props) {
|
|||||||
</SearchButton>
|
</SearchButton>
|
||||||
<ChipHolder className="pf-c-form-control">
|
<ChipHolder className="pf-c-form-control">
|
||||||
<ChipGroup>
|
<ChipGroup>
|
||||||
{(multiple ? value : [value]).map(item => (
|
{(multiple ? value : [value]).map(item =>
|
||||||
<Chip
|
renderItemChip({
|
||||||
key={item.id}
|
item,
|
||||||
onClick={() => removeItem(item)}
|
removeItem,
|
||||||
isReadOnly={!canDelete}
|
canDelete,
|
||||||
>
|
})
|
||||||
{item.name}
|
)}
|
||||||
</Chip>
|
|
||||||
))}
|
|
||||||
</ChipGroup>
|
</ChipGroup>
|
||||||
</ChipHolder>
|
</ChipHolder>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
@@ -121,7 +138,7 @@ function Lookup(props) {
|
|||||||
className="awx-c-modal"
|
className="awx-c-modal"
|
||||||
title={i18n._(t`Select ${header || i18n._(t`Items`)}`)}
|
title={i18n._(t`Select ${header || i18n._(t`Items`)}`)}
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onClose={() => dispatch({ type: 'TOGGLE_MODAL' })}
|
onClose={closeModal}
|
||||||
actions={[
|
actions={[
|
||||||
<Button
|
<Button
|
||||||
key="select"
|
key="select"
|
||||||
@@ -133,27 +150,16 @@ function Lookup(props) {
|
|||||||
>
|
>
|
||||||
{i18n._(t`Select`)}
|
{i18n._(t`Select`)}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
<Button key="cancel" variant="secondary" onClick={closeModal}>
|
||||||
key="cancel"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => dispatch({ type: 'TOGGLE_MODAL' })}
|
|
||||||
>
|
|
||||||
{i18n._(t`Cancel`)}
|
{i18n._(t`Cancel`)}
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SelectList
|
{renderSelectList({
|
||||||
value={selectedItems}
|
state,
|
||||||
options={items}
|
dispatch,
|
||||||
optionCount={count}
|
canDelete,
|
||||||
columns={columns}
|
})}
|
||||||
multiple={multiple}
|
|
||||||
header={header}
|
|
||||||
name={name}
|
|
||||||
qsConfig={qsConfig}
|
|
||||||
readOnly={!canDelete}
|
|
||||||
dispatch={dispatch}
|
|
||||||
/>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@@ -165,27 +171,38 @@ const Item = shape({
|
|||||||
|
|
||||||
Lookup.propTypes = {
|
Lookup.propTypes = {
|
||||||
id: string,
|
id: string,
|
||||||
items: arrayOf(shape({})).isRequired,
|
// items: arrayOf(shape({})).isRequired,
|
||||||
count: number.isRequired,
|
// count: number.isRequired,
|
||||||
// TODO: change to `header`
|
// TODO: change to `header`
|
||||||
header: string,
|
header: string,
|
||||||
name: string,
|
// name: string,
|
||||||
onChange: func.isRequired,
|
onChange: func.isRequired,
|
||||||
value: oneOfType([Item, arrayOf(Item)]),
|
value: oneOfType([Item, arrayOf(Item)]),
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
required: bool,
|
required: bool,
|
||||||
onBlur: func,
|
onBlur: func,
|
||||||
qsConfig: QSConfig.isRequired,
|
qsConfig: QSConfig.isRequired,
|
||||||
|
renderItemChip: func,
|
||||||
|
renderSelectList: func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
Lookup.defaultProps = {
|
Lookup.defaultProps = {
|
||||||
id: 'lookup-search',
|
id: 'lookup-search',
|
||||||
header: null,
|
header: null,
|
||||||
name: null,
|
// name: null,
|
||||||
value: null,
|
value: null,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
required: false,
|
required: false,
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
|
renderItemChip: ({ item, removeItem, canDelete }) => (
|
||||||
|
<Chip
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => removeItem(item)}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Chip>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Lookup as _Lookup };
|
export { Lookup as _Lookup };
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ function SelectList({
|
|||||||
name,
|
name,
|
||||||
qsConfig,
|
qsConfig,
|
||||||
readOnly,
|
readOnly,
|
||||||
dispatch,
|
selectItem,
|
||||||
|
deselectItem,
|
||||||
i18n,
|
i18n,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -36,7 +37,7 @@ function SelectList({
|
|||||||
label={i18n._(t`Selected`)}
|
label={i18n._(t`Selected`)}
|
||||||
selected={value}
|
selected={value}
|
||||||
showOverflowAfter={5}
|
showOverflowAfter={5}
|
||||||
onRemove={item => dispatch({ type: 'DESELECT_ITEM', item })}
|
onRemove={item => deselectItem(item)}
|
||||||
isReadOnly={readOnly}
|
isReadOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -53,8 +54,8 @@ function SelectList({
|
|||||||
name={multiple ? item.name : name}
|
name={multiple ? item.name : name}
|
||||||
label={item.name}
|
label={item.name}
|
||||||
isSelected={value.some(i => i.id === item.id)}
|
isSelected={value.some(i => i.id === item.id)}
|
||||||
onSelect={() => dispatch({ type: 'SELECT_ITEM', item })}
|
onSelect={() => selectItem(item)}
|
||||||
onDeselect={() => dispatch({ type: 'DESELECT_ITEM', item })}
|
onDeselect={() => deselectItem(item)}
|
||||||
isRadio={!multiple}
|
isRadio={!multiple}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -75,7 +76,8 @@ SelectList.propTypes = {
|
|||||||
columns: arrayOf(shape({})).isRequired,
|
columns: arrayOf(shape({})).isRequired,
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
qsConfig: QSConfig.isRequired,
|
qsConfig: QSConfig.isRequired,
|
||||||
dispatch: func.isRequired,
|
selectItem: func.isRequired,
|
||||||
|
deselectItem: func.isRequired,
|
||||||
};
|
};
|
||||||
SelectList.defaultProps = {
|
SelectList.defaultProps = {
|
||||||
multiple: false,
|
multiple: false,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// import { useReducer, useEffect } from 'react';
|
||||||
|
|
||||||
export default function reducer(state, action) {
|
export default function reducer(state, action) {
|
||||||
// console.log(action, state);
|
// console.log(action, state);
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@@ -56,33 +58,13 @@ function toggleModal(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeModal(state) {
|
function closeModal(state) {
|
||||||
// TODO clear QSParams & push history state?
|
|
||||||
// state.clearQSParams();
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// clearQSParams() {
|
|
||||||
// const { qsConfig, history } = this.props;
|
|
||||||
// const parts = history.location.search.replace(/^\?/, '').split('&');
|
|
||||||
// const ns = qsConfig.namespace;
|
|
||||||
// const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
|
||||||
// history.push(`${history.location.pathname}?${otherParts.join('&')}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
export function initReducer({
|
export function initReducer({ value, multiple = false, required = false }) {
|
||||||
id,
|
|
||||||
items,
|
|
||||||
count,
|
|
||||||
header,
|
|
||||||
name,
|
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
multiple = false,
|
|
||||||
required = false,
|
|
||||||
qsConfig,
|
|
||||||
}) {
|
|
||||||
assertCorrectValueType(value, multiple);
|
assertCorrectValueType(value, multiple);
|
||||||
let selectedItems = [];
|
let selectedItems = [];
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -94,7 +76,6 @@ export function initReducer({
|
|||||||
multiple,
|
multiple,
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
required,
|
required,
|
||||||
onChange,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,3 +89,18 @@ function assertCorrectValueType(value, multiple) {
|
|||||||
throw new Error('Lookup value must be an array if `multiple` is set');
|
throw new Error('Lookup value must be an array if `multiple` is set');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// export function useLookup(config) {
|
||||||
|
// const { value, multiple, required, onChange, history } = config;
|
||||||
|
// const [state, dispatch] = useReducer(
|
||||||
|
// config.reducer || reducer,
|
||||||
|
// {
|
||||||
|
// value,
|
||||||
|
// multiple,
|
||||||
|
// required,
|
||||||
|
// },
|
||||||
|
// config.initReducer || initReducer
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// return [state, dispatch];
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user