mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: optimize view form.
This commit is contained in:
@@ -121,18 +121,21 @@ function AccountsDataTable({
|
|||||||
</Tooltip>) : row.name;
|
</Tooltip>) : row.name;
|
||||||
},
|
},
|
||||||
className: 'account_name',
|
className: 'account_name',
|
||||||
|
width: 300,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'code',
|
id: 'code',
|
||||||
Header: 'Code',
|
Header: 'Code',
|
||||||
accessor: 'code',
|
accessor: 'code',
|
||||||
className: 'code',
|
className: 'code',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'type',
|
id: 'type',
|
||||||
Header: 'Type',
|
Header: 'Type',
|
||||||
accessor: 'type.name',
|
accessor: 'type.name',
|
||||||
className: 'type',
|
className: 'type',
|
||||||
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'normal',
|
id: 'normal',
|
||||||
@@ -145,6 +148,7 @@ function AccountsDataTable({
|
|||||||
return (<Icon icon={`arrow-${arrowDirection}`} />);
|
return (<Icon icon={`arrow-${arrowDirection}`} />);
|
||||||
},
|
},
|
||||||
className: 'normal',
|
className: 'normal',
|
||||||
|
width: 75,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'balance',
|
id: 'balance',
|
||||||
@@ -159,6 +163,7 @@ function AccountsDataTable({
|
|||||||
</span>) :
|
</span>) :
|
||||||
(<span class="placeholder">--</span>);
|
(<span class="placeholder">--</span>);
|
||||||
},
|
},
|
||||||
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ import React from 'react';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
|
|
||||||
export default function DashboardInsider({ loading, children, name }) {
|
export default function DashboardInsider({
|
||||||
|
loading,
|
||||||
|
children,
|
||||||
|
name,
|
||||||
|
mount = true,
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={classnames({
|
<div className={classnames({
|
||||||
'dashboard__insider': true,
|
'dashboard__insider': true,
|
||||||
'dashboard__insider--loading': loading,
|
'dashboard__insider--loading': loading,
|
||||||
[`dashboard__insider--${name}`]: !!name,
|
[`dashboard__insider--${name}`]: !!name,
|
||||||
})}>
|
})}>
|
||||||
<LoadingIndicator loading={loading}>
|
<LoadingIndicator loading={loading} mount={mount}>
|
||||||
{ children }
|
{ children }
|
||||||
</LoadingIndicator>
|
</LoadingIndicator>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, {useState, useEffect, useCallback, useMemo} from 'react';
|
import React, {useState, useEffect, useCallback, useMemo} from 'react';
|
||||||
import {Formik, useFormik, ErrorMessage} from "formik";
|
import { useFormik } from "formik";
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
import {
|
import {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
@@ -16,29 +16,47 @@ import {
|
|||||||
import {Row, Col} from 'react-grid-system';
|
import {Row, Col} from 'react-grid-system';
|
||||||
import { ReactSortable } from 'react-sortablejs';
|
import { ReactSortable } from 'react-sortablejs';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import {pick} from 'lodash';
|
import {pick, get} from 'lodash';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import ViewFormConnect from 'connectors/ViewFormPage.connector';
|
import ViewFormConnect from 'connectors/ViewFormPage.connector';
|
||||||
import {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
|
import ErrorMessage from 'components/ErrorMessage';
|
||||||
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
|
import ResourceConnect from 'connectors/Resource.connector';
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
|
||||||
function ViewForm({
|
function ViewForm({
|
||||||
|
resourceName,
|
||||||
columns,
|
columns,
|
||||||
fields,
|
fields,
|
||||||
viewColumns,
|
viewColumns,
|
||||||
viewForm,
|
viewForm,
|
||||||
|
viewFormColumns,
|
||||||
submitView,
|
submitView,
|
||||||
editView,
|
editView,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
getResourceField,
|
||||||
|
getResourceColumn,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [draggedColumns, setDraggedColumn] = useState([]);
|
|
||||||
const [availableColumns, setAvailableColumns] = useState(columns);
|
const [draggedColumns, setDraggedColumn] = useState([
|
||||||
|
...(viewForm && viewForm.columns) ? viewForm.columns.map((column) => {
|
||||||
|
return getResourceColumn(column.field_id);
|
||||||
|
}) : []
|
||||||
|
]);
|
||||||
|
|
||||||
|
const draggedColumnsIds = useMemo(() =>
|
||||||
|
draggedColumns.map(c => c.id), [draggedColumns]);
|
||||||
|
|
||||||
|
const [availableColumns, setAvailableColumns] = useState([
|
||||||
|
...(viewForm && viewForm.columns) ? columns.filter((column) =>
|
||||||
|
draggedColumnsIds.indexOf(column.id) === -1
|
||||||
|
) : columns,
|
||||||
|
]);
|
||||||
|
|
||||||
const defaultViewRole = useMemo(() => ({
|
const defaultViewRole = useMemo(() => ({
|
||||||
field_key: '',
|
field_key: '', comparator: '', value: '', index: 1,
|
||||||
comparator: 'AND',
|
|
||||||
value: '',
|
|
||||||
index: 1,
|
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
@@ -58,20 +76,37 @@ function ViewForm({
|
|||||||
key: Yup.string().required(),
|
key: Yup.string().required(),
|
||||||
index: Yup.string().required(),
|
index: Yup.string().required(),
|
||||||
}),
|
}),
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
const initialEmptyForm = {
|
const initialEmptyForm = useMemo(() => ({
|
||||||
resource_name: '',
|
resource_name: resourceName || '',
|
||||||
name: '',
|
name: '',
|
||||||
logic_expression: '',
|
logic_expression: '',
|
||||||
roles: [
|
roles: [
|
||||||
defaultViewRole,
|
defaultViewRole,
|
||||||
],
|
],
|
||||||
columns: [],
|
columns: [],
|
||||||
};
|
}), [defaultViewRole, resourceName]);
|
||||||
const initialForm = { ...initialEmptyForm, ...viewForm };
|
|
||||||
|
|
||||||
const formik = useFormik({
|
const initialForm = useMemo(() =>
|
||||||
|
({
|
||||||
|
...initialEmptyForm,
|
||||||
|
...viewForm ? {
|
||||||
|
...viewForm,
|
||||||
|
resource_name: viewForm.resource.name,
|
||||||
|
} : {},
|
||||||
|
}),
|
||||||
|
[initialEmptyForm, viewForm]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
setFieldValue,
|
||||||
|
getFieldProps,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
} = useFormik({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
validationSchema: validationSchema,
|
validationSchema: validationSchema,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -86,96 +121,112 @@ function ViewForm({
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
onSubmit: (values) => {
|
onSubmit: (values, { setSubmitting }) => {
|
||||||
if (viewForm && viewForm.id) {
|
if (viewForm && viewForm.id) {
|
||||||
editView(viewForm.id, values).then((response) => {
|
editView(viewForm.id, values).then((response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'the_view_has_been_edited'
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
submitView(values).then((response) => {
|
submitView(values).then((response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'the_view_has_been_submit'
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formik.setFieldValue('columns',
|
setFieldValue('columns',
|
||||||
draggedColumns.map((column, index) => ({
|
draggedColumns.map((column, index) => ({
|
||||||
index, key: column.key,
|
index, key: column.key,
|
||||||
})));
|
})));
|
||||||
}, [draggedColumns, formik]);
|
}, [setFieldValue, draggedColumns]);
|
||||||
|
|
||||||
const conditionalsItems = [
|
const conditionalsItems = useMemo(() => ([
|
||||||
{ value: 'and', label: 'AND' },
|
{ value: 'and', label: 'AND' },
|
||||||
{ value: 'or', label: 'OR' },
|
{ value: 'or', label: 'OR' },
|
||||||
];
|
]), []);
|
||||||
const whenConditionalsItems = [
|
|
||||||
|
const whenConditionalsItems = useMemo(() => ([
|
||||||
{ value: '', label: 'When' },
|
{ value: '', label: 'When' },
|
||||||
];
|
]), []);
|
||||||
|
|
||||||
// Compatotors items.
|
// Compatotors items.
|
||||||
const compatatorsItems = [
|
const compatatorsItems = useMemo(() => ([
|
||||||
{value: '', label: 'Select a compatator'},
|
{value: '', label: 'Compatator'},
|
||||||
{value: 'equals', label: 'Equals'},
|
{value: 'equals', label: 'Equals'},
|
||||||
{value: 'not_equal', label: 'Not Equal'},
|
{value: 'not_equal', label: 'Not Equal'},
|
||||||
{value: 'contain', label: 'Contain'},
|
{value: 'contain', label: 'Contain'},
|
||||||
{value: 'not_contain', label: 'Not Contain'},
|
{value: 'not_contain', label: 'Not Contain'},
|
||||||
];
|
]), []);
|
||||||
|
|
||||||
// Resource fields.
|
// Resource fields.
|
||||||
const resourceFields = useMemo(() => ([
|
const resourceFields = useMemo(() => ([
|
||||||
{value: '', label: 'Select a field'},
|
{value: '', label: 'Select a field'},
|
||||||
...fields.map((field) => ({ value: field.key, label: field.labelName, })),
|
...fields.map((field) => ({ value: field.key, label: field.label_name, })),
|
||||||
]), []);
|
]), [fields]);
|
||||||
|
|
||||||
// Account item of select accounts field.
|
// Account item of select accounts field.
|
||||||
const selectItem = (item, { handleClick, modifiers, query }) => {
|
const selectItem = (item, { handleClick, modifiers, query }) => {
|
||||||
return (<MenuItem text={item.label} key={item.key} onClick={handleClick} />)
|
return (<MenuItem text={item.label} key={item.key} onClick={handleClick} />)
|
||||||
};
|
};
|
||||||
// Handle click new condition button.
|
// Handle click new condition button.
|
||||||
const onClickNewRole = useCallback(() => {
|
const onClickNewRole = useCallback(() => {
|
||||||
formik.setFieldValue('roles', [
|
setFieldValue('roles', [
|
||||||
...formik.values.roles,
|
...values.roles,
|
||||||
{
|
{
|
||||||
...defaultViewRole,
|
...defaultViewRole,
|
||||||
index: formik.values.roles.length + 1,
|
index: values.roles.length + 1,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}, [formik, defaultViewRole]);
|
}, [defaultViewRole, setFieldValue, values]);
|
||||||
|
|
||||||
// Handle click remove view role button.
|
// Handle click remove view role button.
|
||||||
const onClickRemoveRole = useCallback((viewRole, index) => () => {
|
const onClickRemoveRole = useCallback((viewRole, index) => () => {
|
||||||
const viewRoles = [...formik.values.roles];
|
const viewRoles = [...values.roles];
|
||||||
|
|
||||||
|
// Can't continue if view roles equals or less than 1.
|
||||||
|
if (viewRoles.length <= 1) { return; }
|
||||||
|
|
||||||
viewRoles.splice(index, 1);
|
viewRoles.splice(index, 1);
|
||||||
viewRoles.map((role, i) => {
|
viewRoles.map((role, i) => {
|
||||||
role.index = i + 1;
|
role.index = i + 1;
|
||||||
return role;
|
return role;
|
||||||
});
|
});
|
||||||
formik.setFieldValue('roles', viewRoles);
|
setFieldValue('roles', viewRoles);
|
||||||
}, [formik]);
|
}, [values, setFieldValue]);
|
||||||
|
|
||||||
const onClickDeleteView = useCallback(() => {
|
const onClickDeleteView = useCallback(() => {
|
||||||
onDelete && onDelete(viewForm);
|
onDelete && onDelete(viewForm);
|
||||||
}, [onDelete, viewForm]);
|
}, [onDelete, viewForm]);
|
||||||
|
|
||||||
|
const hasError = (path) => get(errors, path) && get(touched, path);
|
||||||
|
|
||||||
|
console.log(errors, touched);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="view-form">
|
<div class="view-form">
|
||||||
<form onSubmit={formik.handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div class="view-form--name-section">
|
<div class="view-form--name-section">
|
||||||
<Row>
|
<Row>
|
||||||
<Col sm={8}>
|
<Col sm={8}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={intl.formatMessage({'id': 'View Name'})}
|
label={intl.formatMessage({'id': 'View Name'})}
|
||||||
className={'form-group--name'}
|
className={'form-group--name'}
|
||||||
intent={formik.errors.name && Intent.DANGER}
|
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||||
helperText={formik.errors.name && formik.errors.label}
|
helperText={<ErrorMessage {...{errors, touched}} name={'name'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
fill={true}>
|
fill={true}>
|
||||||
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
intent={formik.errors.name && Intent.DANGER}
|
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||||
fill={true}
|
fill={true}
|
||||||
{...formik.getFieldProps('name')} />
|
{...getFieldProps('name')} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -183,7 +234,7 @@ function ViewForm({
|
|||||||
|
|
||||||
<H5 className="mb2">Define the conditionals</H5>
|
<H5 className="mb2">Define the conditionals</H5>
|
||||||
|
|
||||||
{formik.values.roles.map((role, index) => (
|
{values.roles.map((role, index) => (
|
||||||
<Row class="view-form__role-conditional">
|
<Row class="view-form__role-conditional">
|
||||||
<Col sm={2} class="flex">
|
<Col sm={2} class="flex">
|
||||||
<div class="mr2 pt1 condition-number">{ index + 1 }</div>
|
<div class="mr2 pt1 condition-number">{ index + 1 }</div>
|
||||||
@@ -196,38 +247,36 @@ function ViewForm({
|
|||||||
|
|
||||||
<Col sm={2}>
|
<Col sm={2}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
intent={formik.getFieldMeta(`roles[${index}].field_key`).error && Intent.DANGER}>
|
intent={hasError(`roles[${index}].field_key`) && Intent.DANGER}>
|
||||||
|
|
||||||
<HTMLSelect
|
<HTMLSelect
|
||||||
options={resourceFields}
|
options={resourceFields}
|
||||||
value={role.field}
|
value={role.field_key}
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
{...formik.getFieldProps(`roles[${index}].field_key`)} />
|
{...getFieldProps(`roles[${index}].field_key`)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col sm={2}>
|
<Col sm={2}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
intent={formik.getFieldMeta(`roles[${index}].comparator`).error && Intent.DANGER}>
|
intent={hasError(`roles[${index}].comparator`) && Intent.DANGER}>
|
||||||
|
|
||||||
<HTMLSelect
|
<HTMLSelect
|
||||||
options={compatatorsItems}
|
options={compatatorsItems}
|
||||||
value={role.comparator}
|
value={role.comparator}
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
{...formik.getFieldProps(`roles[${index}].comparator`)} />
|
{...getFieldProps(`roles[${index}].comparator`)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col sm={5} class="flex">
|
<Col sm={5} class="flex">
|
||||||
<FormGroup>
|
<FormGroup
|
||||||
|
intent={hasError(`roles[${index}].value`) && Intent.DANGER}>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
placeholder={intl.formatMessage({'id': 'value'})}
|
placeholder={intl.formatMessage({'id': 'value'})}
|
||||||
intent={formik.getFieldMeta(`roles[${index}].value`).error && Intent.DANGER}
|
{...getFieldProps(`roles[${index}].value`)} />
|
||||||
{...formik.getFieldProps(`roles[${index}].value`)} />
|
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<Icon icon="mines" />}
|
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||||
iconSize={14}
|
iconSize={14}
|
||||||
className="ml2"
|
className="ml2"
|
||||||
minimal={true}
|
minimal={true}
|
||||||
@@ -237,12 +286,12 @@ function ViewForm({
|
|||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div class="mt1">
|
<div className={'view-form__role-conditions-actions'}>
|
||||||
<Button
|
<Button
|
||||||
minimal={true}
|
minimal={true}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
onClick={onClickNewRole}>
|
onClick={onClickNewRole}>
|
||||||
+ New Conditional
|
New Conditional
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -252,22 +301,24 @@ function ViewForm({
|
|||||||
<FormGroup
|
<FormGroup
|
||||||
label={intl.formatMessage({'id': 'Logic Expression'})}
|
label={intl.formatMessage({'id': 'Logic Expression'})}
|
||||||
className={'form-group--logic-expression'}
|
className={'form-group--logic-expression'}
|
||||||
intent={formik.errors.logic_expression && Intent.DANGER}
|
intent={(errors.logic_expression && touched.logic_expression) && Intent.DANGER}
|
||||||
helperText={formik.errors.logic_expression && formik.errors.logic_expression}
|
helperText={<ErrorMessage {...{errors, touched}} name='logic_expression' />}
|
||||||
inline={true}
|
inline={true}
|
||||||
fill={true}>
|
fill={true}>
|
||||||
|
|
||||||
<InputGroup intent={formik.errors.logic_expression && Intent.DANGER} fill={true}
|
<InputGroup
|
||||||
{...formik.getFieldProps('logic_expression')} />
|
intent={(errors.logic_expression && touched.logic_expression) && Intent.DANGER}
|
||||||
|
fill={true}
|
||||||
|
{...getFieldProps('logic_expression')} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<H5 className={'mb2'}>Columns Preferences</H5>
|
<H5 className={'mb2'}>Columns Preferences</H5>
|
||||||
|
|
||||||
<div class="dragable-columns">
|
<div class="dragable-columns">
|
||||||
<Row>
|
<Row gutterWidth={14}>
|
||||||
<Col sm={4} className="dragable-columns__column">
|
<Col sm={4} className="dragable-columns__column">
|
||||||
<H6 className="dragable-columns__title">Available Columns</H6>
|
<H6 className="dragable-columns__title">Available Columns</H6>
|
||||||
|
|
||||||
@@ -317,11 +368,22 @@ function ViewForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form__floating-footer">
|
<div class="form__floating-footer">
|
||||||
<Button intent={Intent.PRIMARY} type="submit">Submit</Button>
|
<Button
|
||||||
<Button intent={Intent.NONE} type="submit" className="ml2">Cancel</Button>
|
intent={Intent.PRIMARY}
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button intent={Intent.NONE} type="submit" className="ml1">Cancel</Button>
|
||||||
|
|
||||||
{ (viewForm && viewForm.id) && (
|
{ (viewForm && viewForm.id) && (
|
||||||
<Button intent={Intent.DANGER} onClick={onClickDeleteView}>Delete</Button>
|
<Button
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={onClickDeleteView}
|
||||||
|
className={"right mr2"}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -331,4 +393,6 @@ function ViewForm({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
ViewFormConnect,
|
ViewFormConnect,
|
||||||
|
DashboardConnect,
|
||||||
|
ResourceConnect,
|
||||||
)(ViewForm);
|
)(ViewForm);
|
||||||
@@ -6,11 +6,16 @@ import {
|
|||||||
import {
|
import {
|
||||||
getResourceColumns,
|
getResourceColumns,
|
||||||
getResourceFields,
|
getResourceFields,
|
||||||
|
getResourceColumn,
|
||||||
|
getResourceField,
|
||||||
} from 'store/resources/resources.reducer';
|
} from 'store/resources/resources.reducer';
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => ({
|
export const mapStateToProps = (state, props) => ({
|
||||||
getResourceColumns: (resourceSlug) => getResourceColumns(state, resourceSlug),
|
getResourceColumns: (resourceSlug) => getResourceColumns(state, resourceSlug),
|
||||||
getResourceFields: (resourceSlug) => getResourceFields(state, resourceSlug),
|
getResourceFields: (resourceSlug) => getResourceFields(state, resourceSlug),
|
||||||
|
|
||||||
|
getResourceColumn: (columnId) => getResourceColumn(state, columnId),
|
||||||
|
getResourceField: (fieldId) => getResourceField(state, fieldId),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|||||||
@@ -16,18 +16,11 @@ import t from 'store/types';
|
|||||||
|
|
||||||
export const mapStateToProps = (state, props) => {
|
export const mapStateToProps = (state, props) => {
|
||||||
return {
|
return {
|
||||||
getResourceColumns: (resourceSlug) => getResourceColumns(state, resourceSlug),
|
|
||||||
getResourceFields: (resourceSlug) => getResourceFields(state, resourceSlug),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
changePageTitle: pageTitle => dispatch({
|
|
||||||
type: t.CHANGE_DASHBOARD_PAGE_TITLE,
|
|
||||||
pageTitle,
|
|
||||||
}),
|
|
||||||
fetchResourceFields: (resourceSlug) => dispatch(fetchResourceFields({ resourceSlug })),
|
|
||||||
fetchResourceColumns: (resourceSlug) => dispatch(fetchResourceColumns({ resourceSlug })),
|
|
||||||
fetchView: (id) => dispatch(fetchView({ id })),
|
fetchView: (id) => dispatch(fetchView({ id })),
|
||||||
submitView: (form) => dispatch(submitView({ form })),
|
submitView: (form) => dispatch(submitView({ form })),
|
||||||
editView: (id, form) => dispatch(editView({ id, form })),
|
editView: (id, form) => dispatch(editView({ id, form })),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState, useCallback} from 'react';
|
||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
@@ -23,8 +23,10 @@ function ViewFormPage({
|
|||||||
deleteView,
|
deleteView,
|
||||||
}) {
|
}) {
|
||||||
const { resource_slug: resourceSlug, view_id: viewId } = useParams();
|
const { resource_slug: resourceSlug, view_id: viewId } = useParams();
|
||||||
|
|
||||||
const columns = getResourceColumns('accounts');
|
const columns = getResourceColumns('accounts');
|
||||||
const fields = getResourceFields('accounts');
|
const fields = getResourceFields('accounts');
|
||||||
|
|
||||||
const viewForm = (viewId) ? getViewMeta(viewId) : null;
|
const viewForm = (viewId) ? getViewMeta(viewId) : null;
|
||||||
|
|
||||||
const [stateDeleteView, setStateDeleteView] = useState(null);
|
const [stateDeleteView, setStateDeleteView] = useState(null);
|
||||||
@@ -35,7 +37,7 @@ function ViewFormPage({
|
|||||||
} else {
|
} else {
|
||||||
changePageTitle('New Custom View');
|
changePageTitle('New Custom View');
|
||||||
}
|
}
|
||||||
}, [viewId]);
|
}, [viewId, changePageTitle]);
|
||||||
|
|
||||||
const fetchHook = useAsync(async () => {
|
const fetchHook = useAsync(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -47,21 +49,28 @@ function ViewFormPage({
|
|||||||
]);
|
]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDeleteView = (view) => { setStateDeleteView(view); };
|
const handleDeleteView = useCallback((view) => {
|
||||||
const handleCancelDeleteView = () => { setStateDeleteView(null); };
|
setStateDeleteView(view);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleConfirmDeleteView = () => {
|
const handleCancelDeleteView = useCallback(() => {
|
||||||
|
setStateDeleteView(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleConfirmDeleteView = useCallback(() => {
|
||||||
deleteView(stateDeleteView.id).then((response) => {
|
deleteView(stateDeleteView.id).then((response) => {
|
||||||
setStateDeleteView(null);
|
setStateDeleteView(null);
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'the_custom_view_has_been_deleted',
|
message: 'the_custom_view_has_been_deleted',
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
};
|
}, [deleteView, stateDeleteView]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider name={'view-form'} loading={fetchHook.loading}>
|
<DashboardInsider name={'view-form'} loading={fetchHook.loading} mount={false}>
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<ViewForm
|
<ViewForm
|
||||||
|
resourceName={resourceSlug}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
viewForm={viewForm}
|
viewForm={viewForm}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const submitView = ({ form }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const editView = ({ id, form }) => {
|
export const editView = ({ id, form }) => {
|
||||||
return (dispatch) => ApiService.post(`views/${id}`);
|
return (dispatch) => ApiService.post(`views/${id}`, form);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteView = ({ id }) => {
|
export const deleteView = ({ id }) => {
|
||||||
|
|||||||
@@ -1,22 +1,41 @@
|
|||||||
import { createReducer } from "@reduxjs/toolkit";
|
import { createReducer } from "@reduxjs/toolkit";
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
import { pickItemsFromIds } from 'store/selectors'
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
resourceFields: {
|
fields: {},
|
||||||
// resource name => { field_id }
|
columns: {},
|
||||||
},
|
resourceFields: {},
|
||||||
resourceColumns: {},
|
resourceColumns: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(initialState, {
|
export default createReducer(initialState, {
|
||||||
[t.RESOURCE_COLUMNS_SET]: (state, action) => {
|
[t.RESOURCE_COLUMNS_SET]: (state, action) => {
|
||||||
state.resourceColumns[action.resource_slug] = action.columns;
|
const _columns = {};
|
||||||
|
|
||||||
|
action.columns.forEach((column) => {
|
||||||
|
_columns[column.id] = column;
|
||||||
|
});
|
||||||
|
state.columns = {
|
||||||
|
...state.columns,
|
||||||
|
..._columns,
|
||||||
|
};
|
||||||
|
state.resourceColumns[action.resource_slug] = action.columns.map(c => c.id);
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.RESOURCE_FIELDS_SET]: (state, action) => {
|
[t.RESOURCE_FIELDS_SET]: (state, action) => {
|
||||||
state.resourceFields[action.resource_slug] = action.fields;
|
const _fields = {};
|
||||||
|
|
||||||
|
action.fields.forEach((field) => {
|
||||||
|
_fields[field.id] = field;
|
||||||
|
});
|
||||||
|
state.fields = {
|
||||||
|
...state.fields,
|
||||||
|
..._fields,
|
||||||
|
};
|
||||||
|
state.resourceFields[action.resource_slug] = action.fields.map(f => f.id);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource fields of the given resource slug.
|
* Retrieve resource fields of the given resource slug.
|
||||||
@@ -24,9 +43,10 @@ export default createReducer(initialState, {
|
|||||||
* @param {String} resourceSlug
|
* @param {String} resourceSlug
|
||||||
*/
|
*/
|
||||||
export const getResourceFields = (state, resourceSlug) => {
|
export const getResourceFields = (state, resourceSlug) => {
|
||||||
const resourceFields = state.resources.resourceFields[resourceSlug];
|
const resourceIds = state.resources.resourceFields[resourceSlug];
|
||||||
return resourceFields ? Object.values(resourceFields) : [];
|
const items = state.resources.fields;
|
||||||
}
|
return pickItemsFromIds(items, resourceIds);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource columns of the given resource slug.
|
* Retrieve resource columns of the given resource slug.
|
||||||
@@ -34,6 +54,25 @@ export const getResourceFields = (state, resourceSlug) => {
|
|||||||
* @param {String} resourceSlug -
|
* @param {String} resourceSlug -
|
||||||
*/
|
*/
|
||||||
export const getResourceColumns = (state, resourceSlug) => {
|
export const getResourceColumns = (state, resourceSlug) => {
|
||||||
const resourceColumns = state.resources.resourceColumns[resourceSlug];
|
const resourceIds = state.resources.resourceColumns[resourceSlug];
|
||||||
return resourceColumns ? Object.values(resourceColumns) : [];
|
const items = state.resources.columns;
|
||||||
}
|
return pickItemsFromIds(items, resourceIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Number} fieldId
|
||||||
|
*/
|
||||||
|
export const getResourceField = (state, fieldId) => {
|
||||||
|
return state.resources.fields[fieldId];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Number} columnId
|
||||||
|
*/
|
||||||
|
export const getResourceColumn = (state, columnId) => {
|
||||||
|
return state.resources.columns[columnId];
|
||||||
|
};
|
||||||
@@ -134,6 +134,14 @@
|
|||||||
background-color: #CFDCEE;
|
background-color: #CFDCEE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tr.no-results{
|
||||||
|
.td{
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr .th.expander,
|
.tr .th.expander,
|
||||||
|
|||||||
@@ -80,12 +80,12 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='ht
|
|||||||
.#{$ns}-html-select select,
|
.#{$ns}-html-select select,
|
||||||
.#{$ns}-select select{
|
.#{$ns}-select select{
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border-radius: 0;
|
border-radius: 2px;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
&:hover{
|
&:hover{
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 0 0;
|
box-shadow: none;
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #ced4da;
|
||||||
}
|
}
|
||||||
&:focus{
|
&:focus{
|
||||||
@@ -149,16 +149,16 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='ht
|
|||||||
.#{$ns}-control {
|
.#{$ns}-control {
|
||||||
|
|
||||||
input:checked ~ .#{$ns}-control-indicator {
|
input:checked ~ .#{$ns}-control-indicator {
|
||||||
box-shadow: 0 0 0 transparent;
|
box-shadow: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
&:hover input:checked ~ .#{$ns}-control-indicator {
|
&:hover input:checked ~ .#{$ns}-control-indicator {
|
||||||
box-shadow: 0 0 0 transparent;
|
box-shadow: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
input:not(:disabled):active:checked ~ .#{$ns}-control-indicator {
|
input:not(:disabled):active:checked ~ .#{$ns}-control-indicator {
|
||||||
box-shadow: 0 0 0 transparent;
|
box-shadow: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
input:disabled:checked ~ .#{$ns}-control-indicator {
|
input:disabled:checked ~ .#{$ns}-control-indicator {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tbody{
|
.tbody{
|
||||||
.tr .td{
|
.tr:not(.no-results) .td{
|
||||||
padding-top: 0.4rem;
|
padding-top: 0.4rem;
|
||||||
padding-bottom: 0.4rem;
|
padding-bottom: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__insider{
|
&__insider{
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&--loading{
|
&--loading{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
.dashboard__insider--view-form{
|
.dashboard__insider--view-form{
|
||||||
padding-left: 25px;
|
padding-left: 25px;
|
||||||
padding-right: 25px;
|
padding-right: 25px;
|
||||||
|
padding-bottom: 90px;
|
||||||
|
|
||||||
.view-form--name-section{
|
.view-form--name-section{
|
||||||
margin-left: -25px;
|
margin-left: -25px;
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--logic-expression-section{
|
&--logic-expression-section{
|
||||||
padding: 20px 25px;
|
padding: 30px 25px;
|
||||||
margin: 1rem -25px 1.5rem;
|
margin: 1rem -25px 1.5rem;
|
||||||
background: #fbfafa;
|
background: #fbfafa;
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.condition-number{
|
.condition-number{
|
||||||
color: #666;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-form-group.#{$ns}-inline{
|
.#{$ns}-form-group.#{$ns}-inline{
|
||||||
@@ -61,7 +62,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__title{
|
&__title{
|
||||||
color: #666;
|
color: #888;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,5 +90,13 @@
|
|||||||
|
|
||||||
&__role-conditional{
|
&__role-conditional{
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.bp3-form-group{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__role-conditions-actions{
|
||||||
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
$sidebar-background: #01194E;
|
$sidebar-background: #01194E;
|
||||||
$sidebar-text-color: #fff;
|
$sidebar-text-color: #fff;
|
||||||
$sidebar-width: 220px;
|
$sidebar-width: 220px;
|
||||||
$sidebar-menu-item-color: #a8b1c7;
|
$sidebar-menu-item-color: #b8c0d5;
|
||||||
|
|
||||||
$sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
$sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user