Commit 4141db61 authored by Shani's avatar Shani

Merge branch 'BRCD-1683' into 'dev'

BRCD-1683 feat(RangeField) - added new field type 'Range' can be multi + Report support

See merge request sdoc/brcloud!940
parents 0085ccce 0d4830a5
......@@ -754,6 +754,10 @@ ul.revision-history-list li.active .cycle {
}
/* ~Chart */
.screen-height-70 {
max-height: 70vh;
}
.scrollbox {
overflow: auto;
background:
......
......@@ -9,6 +9,7 @@ import mainMenu from '../config/mainMenu.json';
import eventsConfig from '../config/events.json';
import ratesConfig from '../config/rates.json';
import collectionsConfig from '../config/collections.json';
import customFieldsConfig from '../config/customFields.json';
/**
* Get data from config files
......@@ -39,6 +40,8 @@ export const getConfig = (key, defaultValue = null) => {
break;
case 'collections': configCache = configCache.set('collections', Immutable.fromJS(collectionsConfig));
break;
case 'customFields': configCache = configCache.set('customFields', Immutable.fromJS(customFieldsConfig));
break;
default: console.log(`Config caregory not exists ${path}`);
}
}
......
......@@ -99,7 +99,11 @@ class CustomFields extends Component {
}
onChangeField = (entity, index, id, value) => {
this.props.dispatch(updateSetting(getSettingsKey(entity), getSettingsPath(entity, ['fields', index, id]), value));
if (value === undefined) {
this.props.dispatch(removeSettingField(getSettingsKey(entity), getSettingsPath(entity, ['fields', index, id])));
} else {
this.props.dispatch(updateSetting(getSettingsKey(entity), getSettingsPath(entity, ['fields', index, id]), value));
}
};
onRemoveField = (entity, index) => {
......
......@@ -60,7 +60,7 @@ class CustomersList extends Component {
}, {
type: 'import',
label: 'Import',
actionStyle: 'default',
actionStyle: 'primary',
showIcon: true,
onClick: this.onClickImprt,
actionSize: 'xsmall',
......
......@@ -33,7 +33,7 @@ class CycleData extends Component {
reloadCycleData: () => {},
baseFilter: {},
showConfirmAllButton: true,
isCycleConfirmed:false,
isCycleConfirmed: false,
currency: '',
invoicesNum: 0,
};
......@@ -234,16 +234,16 @@ class CycleData extends Component {
return moment.unix(sentDate).format(globalSetting.datetimeFormat);
};
downloadTaxURL = (billrunKey) =>
downloadTaxURL = billrunKey =>
`${getConfig('serverUrl')}/api/report?action=taxationReport&report={"billrun_key":"${billrunKey}"}&type=csv`;
parseTaxDownload = (entity) => {
const { billrunKey } = this.props
const downloadUrl = this.downloadTaxURL( billrunKey );
const { billrunKey } = this.props;
const downloadUrl = this.downloadTaxURL(billrunKey);
return (
<form method="post" action={downloadUrl} target="_blank">
<Button className={entity.actionClass} bsStyle={entity.actionStyle} bsSize={entity.actionSize} type="submit">
{entity.label}
{entity.label}
</Button>
</form>
);
......@@ -252,24 +252,22 @@ class CycleData extends Component {
getListActions = () => {
const { showConfirmAllButton, isCycleConfirmed } = this.props;
return [{
label: 'Confirm All',
actionStyle: 'default',
show :showConfirmAllButton,
showIcon: false,
onClick: this.onClickConfirmAll,
actionSize: 'xsmall',
actionClass: 'btn-primary',
},
{
label: 'Download Taxation compliance report',
actionStyle: 'default',
show : isCycleConfirmed,
showIcon: false,
renderFunc : this.parseTaxDownload,
actionSize: 'xsmall',
actionClass: 'btn-primary',
}
];
label: 'Confirm All',
actionStyle: 'primary',
show: showConfirmAllButton,
showIcon: false,
onClick: this.onClickConfirmAll,
actionSize: 'xsmall',
actionClass: 'btn-primary',
}, {
label: 'Download Taxation compliance report',
actionStyle: 'primary',
show: isCycleConfirmed,
showIcn: false,
renderFunc: this.parseTaxDownload,
actionSize: 'xsmall',
actionClass: 'btn-primary',
}];
}
onCloseConfirmationModal = () => {
......@@ -314,7 +312,7 @@ class CycleData extends Component {
{ id: 'attributes.lastname', title: 'Customer Last Name', sort: true, parser: this.parseCycleDataLastName },
{ id: 'totals.after_vat_rounded', title: 'Invoice Total', sort: true, parser: this.parseCycleDataInvoiceTotal },
{ id: 'subss', title: '# of Subscribers', parser: this.parseCycleDataSubscriptionNum },
{ id: 'invoice_id', title: 'Invoice ID', 'sort': true, parser: this.parseCycleDataInvoiceId },
{ id: 'invoice_id', title: 'Invoice ID', sort: true, parser: this.parseCycleDataInvoiceId },
{ id: 'download', title: 'Invoice', parser: this.parseCycleDataDownload },
{ id: 'confirm', title: 'Confirm', parser: this.parseCycleDataConfirm },
{ id: 'email_sent', title: 'Email Sent', sort: true, parser: this.parseCycleDataEmailSent, display: displayEmailSent },
......
import React, { PropTypes } from 'react';
import { Button } from 'react-bootstrap';
const CreateButton = ({ label, onClick, type, disabled, title, buttonStyle }) => (
const CreateButton = ({ label, onClick, type, action, disabled, title, buttonStyle }) => (
<Button bsSize="xsmall" className="btn-primary" onClick={onClick} style={buttonStyle} disabled={disabled} title={title}>
<i className="fa fa-plus" />&nbsp;{label}{type.length > 0 && ` ${type}`}
<i className="fa fa-plus" />
{action.length > 0 && ` ${action}`}
{label.length > 0 && ` ${label}`}
{type.length > 0 && ` ${type}`}
</Button>
);
CreateButton.defaultProps = {
label: 'Create New',
label: 'New',
action: 'Create',
type: '',
title: '',
disabled: false,
......@@ -19,6 +23,7 @@ CreateButton.defaultProps = {
CreateButton.propTypes = {
label: PropTypes.string,
action: PropTypes.string,
type: PropTypes.string,
title: PropTypes.string,
buttonStyle: PropTypes.object,
......
......@@ -26,6 +26,7 @@ class EntityField extends Component {
isFieldTags: this.props.field.get('multiple', false) && !this.props.field.get('select_list', false),
isFieldSelect: this.props.field.get('select_list', false),
isFieldBoolean: this.props.field.get('type', '') === 'boolean',
isFieldRanges: this.props.field.get('type', '') === 'ranges',
fieldPath: this.props.field.get('field_name', '').split('.'),
isRemoveField: ['params'].includes(this.props.field.get('field_name', '').split('.')[0]),
}
......@@ -41,17 +42,22 @@ class EntityField extends Component {
const noDefaultValueVal = this.getNoDefaultValueVal();
const defaultValue = field.get('default_value', noDefaultValueVal);
if (defaultValue !== null) {
this.props.onChange(fieldPath, defaultValue);
this.props.onChange(fieldPath, defaultValue);
}
}
}
}
getNoDefaultValueVal = (byConfig = true) => {
const { field } = this.props;
const { isFieldBoolean, isFieldTags, isFieldSelect } = this.state;
const { isFieldBoolean, isFieldTags, isFieldSelect, isFieldRanges } = this.state;
if (isFieldBoolean) {
return false;
}
if (isFieldRanges) {
// const defaultRangValue = Immutable.Map({ from: '', to: '' });
// return Immutable.List([defaultRangValue]);
return Immutable.List();
}
if (!byConfig) {
return null;
}
......@@ -86,22 +92,32 @@ class EntityField extends Component {
const { fieldPath } = this.state;
const multi = field.get('multiple', false);
if (multi) {
this.props.onChange(fieldPath, val.split(','));
this.props.onChange(fieldPath, val.split(','));
} else {
this.props.onChange(fieldPath, val);
}
}
onChangeRange = (val) => {
const { fieldPath } = this.state;
this.props.onChange(fieldPath, val);
}
onChangeTags = (val) => {
const { fieldPath } = this.state;
this.props.onChange(fieldPath, val);
}
getFieldValue = () => {
const { fieldPath, isFieldTags, isFieldBoolean } = this.state;
const { fieldPath, isFieldTags, isFieldBoolean, isFieldRanges } = this.state;
const { entity, editable } = this.props;
if (isFieldRanges) {
return entity.getIn(fieldPath, undefined);
// return entity.getIn(fieldPath, { from: '', to: '' });
}
if (isFieldBoolean) {
return entity.getIn(fieldPath, '');
const booleanValue = entity.getIn(fieldPath, '');
return (booleanValue === '') ? booleanValue : [true, 1, 'true'].includes(booleanValue);
}
const fieldVal = entity.getIn(fieldPath, []);
if (isFieldTags && editable) {
......@@ -133,8 +149,21 @@ class EntityField extends Component {
renderField = () => {
const { editable, field } = this.props;
const { isFieldTags, isFieldSelect, isFieldBoolean } = this.state;
const { isFieldTags, isFieldSelect, isFieldBoolean, isFieldRanges } = this.state;
const value = this.getFieldValue();
if (isFieldRanges) {
const multi = field.get('multiple', false);
return (
<Field
fieldType="ranges"
onChange={this.onChangeRange}
value={value}
multi={multi}
editable={editable}
label={field.get('title', field.get('field_name', ''))}
/>
);
}
if (isFieldBoolean) {
const checkboxStyle = { height: 29, marginTop: 8 };
return (
......
......@@ -244,7 +244,7 @@ class EntityList extends Component {
const defaultActions = [{
type: 'refresh',
label: 'Refresh',
actionStyle: 'default',
actionStyle: 'primary',
showIcon: true,
onClick: this.onClickRefresh,
actionSize: 'xsmall',
......@@ -252,7 +252,7 @@ class EntityList extends Component {
}, {
type: 'add',
label: 'Add New',
actionStyle: 'default',
actionStyle: 'primary',
showIcon: true,
onClick: this.onClickNew,
actionSize: 'xsmall',
......
......@@ -14,6 +14,8 @@ import Radio from './types/Radio';
import Salutation from './types/Salutation';
import ToggeledInput from './types/ToggeledInput';
import TextEditor from './types/TextEditor';
import Ranges from './types/Ranges';
import Range from './types/Range';
class Field extends PureComponent {
......@@ -47,7 +49,7 @@ class Field extends PureComponent {
}
createInput = () => {
const { fieldType, required, label, style, className, ...inputProps } = this.props;
const { fieldType, style, className, ...inputProps } = this.props;
switch (fieldType) {
case 'number':
return (<Number {...inputProps} />);
......@@ -64,19 +66,23 @@ class Field extends PureComponent {
case 'unlimited':
return (<Unlimitd {...inputProps} />);
case 'toggeledInput':
return (<ToggeledInput {...inputProps} label={label} />);
return (<ToggeledInput {...inputProps} />);
case 'checkbox':
return (<Checkbox {...inputProps} label={label} />);
return (<Checkbox {...inputProps} />);
case 'radio':
return (<Radio {...inputProps} label={label} />);
return (<Radio {...inputProps} />);
case 'salutation':
return (<Salutation {...inputProps} />);
case 'textEditor':
return (<TextEditor {...inputProps} />);
case 'select':
return (<Select {...inputProps} />);
case 'ranges':
return (<Ranges {...inputProps} />);
case 'range':
return (<Range {...inputProps} />);
default:
return (<Text {...inputProps} required={required} />);
return (<Text {...inputProps} />);
}
}
......
import React, { PureComponent, PropTypes } from 'react';
import Immutable from 'immutable';
import { InputGroup } from 'react-bootstrap';
import Field from '../';
class Range extends PureComponent {
static propTypes = {
value: PropTypes.instanceOf(Immutable.Map),
inputProps: PropTypes.object,
editable: PropTypes.bool,
compact: PropTypes.bool,
placeholder: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
from: PropTypes.string,
to: PropTypes.string,
}),
]),
onChange: PropTypes.func,
};
static defaultProps = {
value: Immutable.Map({ from: '', to: '' }),
placeholder: { from: '', to: '' },
inputProps: {},
editable: true,
compact: false,
onChange: () => {},
};
onChangeFrom = (e) => {
const { value } = this.props;
const from = this.getValue(e);
this.props.onChange(value.set('from', from));
}
onChangeTo = (e) => {
const { value } = this.props;
const to = this.getValue(e);
this.props.onChange(value.set('to', to));
}
getValue = (e) => {
const { inputProps: { fieldType = 'text' } } = this.props;
switch (fieldType) {
case 'date':
case 'select':
return e;
default:
return e.target.value;
}
}
render() {
const {
onChange,
value,
placeholder,
editable,
compact,
inputProps: { fieldType = 'text' },
...otherProps
} = this.props;
const valueFrom = Immutable.Map.isMap(value) ? value.get('from', '') : '';
const valueTo = Immutable.Map.isMap(value) ? value.get('to', '') : '';
const placeholderFrom = typeof placeholder['from'] !== 'undefined' ? placeholder.from : '';
const placeholderTo = typeof placeholder['to'] !== 'undefined' ? placeholder.to : '';
if (!editable) {
return (
<span className="non-editable-field">{`${valueFrom} - ${valueTo}`}</span>
);
}
return (
<InputGroup style={{ width: '100%' }}>
{!compact && (
<InputGroup.Addon><small>From</small></InputGroup.Addon>
)}
<Field
{...otherProps}
fieldType={fieldType}
value={valueFrom}
onChange={this.onChangeFrom}
placeholder={placeholderFrom}
/>
{!compact && (
<InputGroup.Addon><small>To</small></InputGroup.Addon>
)}
{compact && (
<InputGroup.Addon><small>-</small></InputGroup.Addon>
)}
<Field
{...otherProps}
fieldType={fieldType}
value={valueTo}
onChange={this.onChangeTo}
placeholder={placeholderTo}
/>
</InputGroup>
);
}
}
export default Range;
import React, { PureComponent, PropTypes } from 'react';
import Immutable from 'immutable';
import uuid from 'uuid';
import { FormGroup, InputGroup, Button } from 'react-bootstrap';
import CreateButton from '../../Elements/CreateButton';
import Range from './Range';
class Ranges extends PureComponent {
static propTypes = {
id: PropTypes.string,
value: PropTypes.instanceOf(Immutable.List),
label: PropTypes.string,
multi: PropTypes.bool,
editable: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func,
};
static defaultProps = {
id: undefined,
value: Immutable.List([]),
label: '',
inputProps: {},
multi: false,
editable: true,
disabled: true,
onChange: () => {},
};
constructor(props) {
super(props);
this.state = {
id: props.id || uuid.v4(),
};
}
onChange = (rangeValue, index) => {
const { value } = this.props;
this.props.onChange(value.set(index, rangeValue));
}
onAdd = () => {
const { value } = this.props;
this.props.onChange(value.push(Immutable.Map({ from: '', to: '' })));
}
onRemove = (index) => {
const { value } = this.props;
this.props.onChange(value.delete(index));
}
render() {
const {
onChange,
value,
editable,
disabled,
label,
multi,
...otherProps
} = this.props;
const { id } = this.state;
if (!editable) {
return (
<span>
{value.map((rangeValue, index) => (
<span key={`range_${id}_${index}`}>
{index > 0 && ", "}
<Range value={rangeValue} editable={false} />
</span>
))}
</span>
);
}
const ranges = value.map((rangeValue, index) => {
const onChangeRange = (v) => {
this.onChange(v, index);
};
const onRemoveRange = () => {
this.onRemove(index);
};
return (
<FormGroup key={`range_${id}_${index}`} className="rangesField form-inner-edit-row">
<InputGroup>
<Range
{...otherProps}
value={rangeValue}
onChange={onChangeRange}
editable={editable}
disabled={disabled}
/>
{ editable && (
<InputGroup.Button>
<Button onClick={onRemoveRange} disabled={disabled} >
<i className="fa fa-fw fa-trash-o danger-red" />
</Button>
</InputGroup.Button>
) }
</InputGroup>
</FormGroup>
);
});
const hasEmptyValue = value.size > 0 && value.some(range => range.get('from', '') === '' || range.get('to', '') === '');
return (
<div>
{ranges}
{editable && (multi || (!multi && value.size === 0)) && (
<CreateButton
buttonStyle={{ marginTop: 5, marginBottom: 10 }}
onClick={this.onAdd}
action="Add"
label=""
type={label}
disabled={disabled || hasEmptyValue}
/>
)}
</div>
);
}
}
export default Ranges;
......@@ -31,6 +31,7 @@ class List extends Component {
onSort: () => {},
sort: Immutable.Map(),
removeText: 'Remove',
className: '',
enableEnabled: false,
onClickEnabled: () => {},
actions: [],
......
......@@ -302,7 +302,7 @@ class RevisionList extends Component {
const removeConfirmMessage = 'Are you sure you want to remove this revision?';
return (
<div>
<List items={items} fields={fields} edit={false} actions={actions} />
<List items={items} fields={fields} edit={false} actions={actions} className="scrollbox screen-height-70" />
{ activeItem &&
<CloseActionBox
itemName={itemName}
......
[
{
"id": "text",
"title": "Text",
"unique": true,
"mandatory": true,
"editable": true,
"display": true,
"showInList": true,
"searchable": true,
"multiple": true,
"selectOptions": true
}, {
"id": "boolean",
"title": "Boolean",
"unique": false,
"mandatory": false,
"searchable": false,
"multiple": false,
"selectOptions": false
}, {
"id": "textarea",
"title": "Text Area",
"multiple": false,
"selectOptions": false
}, {
"id": "ranges",
"title": "Range",
"selectOptions": false,
"include": ["subscriber"]
}
]
......@@ -212,8 +212,8 @@ export default {
{ id: 'last_hours', title: 'Last (hours)', include: ['fieldid:urt'], type: 'number', suffix: 'Hours' },
{ id: 'eq', title: 'Equals', include: ['date', 'boolean', 'fieldid:billrun_status', 'fieldid:logfile_status'] }, // 'Equals'
{ id: 'in', title: 'Equals', include: ['string', 'number'], exclude: ['fieldid:billrun_status', 'fieldid:logfile_status'] },
{ id: 'ne', title: 'Does Not equal', include: ['boolean'], exclude: [] }, // 'Not equals'
{ id: 'nin', title: 'Does Not equal', include: ['string', 'number'], exclude: ['fieldid:billrun_status', 'fieldid:logfile_status'] },
{ id: 'ne', title: 'Does not equal', include: ['boolean'], exclude: [] }, // 'Not equals'
{ id: 'nin', title: 'Does not equal', include: ['string', 'number'], exclude: ['fieldid:billrun_status', 'fieldid:logfile_status'] },
{ id: 'lt', title: '<', include: ['number', 'date', 'fieldid:billrun'], exclude: [] }, // 'Less than'
{ id: 'lte', title: '<=', include: ['number', 'date', 'fieldid:billrun'], exclude: [] }, // 'Less than or equals'
{ id: 'gt', title: '>', include: ['number', 'date', 'fieldid:billrun'], exclude: [] }, // 'Greater than'
......@@ -221,9 +221,11 @@ export default {
{ id: 'like', title: 'Contains', include: ['string', 'number'], exclude: ['fieldid:logfile_status'] },
{ id: 'starts_with', title: 'Starts with', include: ['string'], exclude: ['fieldid:logfile_status'] },
{ id: 'ends_with', title: 'Ends with', include: ['string'], exclude: ['fieldid:logfile_status'] },
{ id: 'in_range', title: 'Include‎', include: ['ranges', 'range'] },
{ id: 'nin_range', title: 'Does not include‎', include: ['ranges', 'range'] },
{ id: 'exists', title: 'Exists', type: 'boolean',
include: ['string', 'number', 'boolean', 'date'],
exclude: [ 'fieldid:billrun_status', 'fieldid:logfile_status'],
include: ['string', 'number', 'boolean', 'date', 'ranges', 'range'],
exclude: ['fieldid:billrun_status', 'fieldid:logfile_status'],
options: ['yes', 'no'],
},
],
......@@ -231,12 +233,12 @@ export default {
{ id: 'group', title: 'Group', include: ['string', 'number', 'boolean', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'sum', title: 'Sum', include: ['number'], exclude: ['fieldid:count_group'] },
{ id: 'avg', title: 'Average', include: ['number'], exclude: ['fieldid:count_group'] },
{ id: 'first', title: 'First', include: ['string', 'number', 'boolean', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'last', title: 'Last', include: ['string', 'number', 'boolean', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'first', title: 'First', include: ['string', 'number', 'boolean', 'date', 'ranges', 'range'], exclude: ['fieldid:count_group'] },
{ id: 'last', title: 'Last', include: ['string', 'number', 'boolean', 'date', 'ranges', 'range'], exclude: ['fieldid:count_group'] },
{ id: 'max', title: 'Max', include: ['number', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'min', title: 'Min', include: ['number', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'push', title: 'List', include: ['string', 'number', 'boolean', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'addToSet', title: 'Unique List', include: ['string', 'number', 'boolean', 'date'], exclude: ['fieldid:count_group'] },
{ id: 'addToSet', title: 'Unique List', include: ['string', 'number', 'boolean', 'date', 'ranges', 'range'], exclude: ['fieldid:count_group'] },
{ id: 'count', title: 'Count', include: ['fieldid:count_group'] },
],
outputFormats: [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment