mirror of https://github.com/apache/superset.git
Update time filter to use `react-datetime` (#5819)
* Update time filter to use react-datetime * Clean up code * Many small fixes and improvements * Fix small things
This commit is contained in:
parent
6d573724aa
commit
6c9be9d67b
File diff suppressed because it is too large
Load Diff
|
@ -92,12 +92,11 @@
|
||||||
"react-addons-css-transition-group": "^15.6.0",
|
"react-addons-css-transition-group": "^15.6.0",
|
||||||
"react-addons-shallow-compare": "^15.4.2",
|
"react-addons-shallow-compare": "^15.4.2",
|
||||||
"react-bootstrap": "^0.31.5",
|
"react-bootstrap": "^0.31.5",
|
||||||
"react-bootstrap-datetimepicker": "0.0.22",
|
|
||||||
"react-bootstrap-dialog": "^0.10.0",
|
"react-bootstrap-dialog": "^0.10.0",
|
||||||
"react-bootstrap-slider": "2.1.5",
|
"react-bootstrap-slider": "2.1.5",
|
||||||
"react-bootstrap-table": "^4.3.1",
|
"react-bootstrap-table": "^4.3.1",
|
||||||
"react-color": "^2.13.8",
|
"react-color": "^2.13.8",
|
||||||
"react-datetime": "2.14.0",
|
"react-datetime": "^2.14.0",
|
||||||
"react-dnd": "^2.5.4",
|
"react-dnd": "^2.5.4",
|
||||||
"react-dnd-html5-backend": "^2.5.4",
|
"react-dnd-html5-backend": "^2.5.4",
|
||||||
"react-dom": "^15.6.2",
|
"react-dom": "^15.6.2",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.rdtPicker table {
|
||||||
|
font-size: 12;
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import {
|
||||||
DropdownButton,
|
DropdownButton,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
|
Glyphicon,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
Label,
|
Label,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
@ -14,11 +15,11 @@ import {
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
|
import Datetime from 'react-datetime';
|
||||||
import 'react-datetime/css/react-datetime.css';
|
import 'react-datetime/css/react-datetime.css';
|
||||||
import DateTimeField from 'react-bootstrap-datetimepicker';
|
|
||||||
import 'react-bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import './DateFilterControl.css';
|
||||||
import ControlHeader from '../ControlHeader';
|
import ControlHeader from '../ControlHeader';
|
||||||
import { t } from '../../../locales';
|
import { t } from '../../../locales';
|
||||||
import PopoverSection from '../../../components/PopoverSection';
|
import PopoverSection from '../../../components/PopoverSection';
|
||||||
|
@ -45,7 +46,6 @@ const TIME_GRAIN_OPTIONS = ['seconds', 'minutes', 'hours', 'days', 'weeks', 'mon
|
||||||
const MOMENT_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss';
|
const MOMENT_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss';
|
||||||
const DEFAULT_SINCE = moment().startOf('day').subtract(7, 'days').format(MOMENT_FORMAT);
|
const DEFAULT_SINCE = moment().startOf('day').subtract(7, 'days').format(MOMENT_FORMAT);
|
||||||
const DEFAULT_UNTIL = moment().startOf('day').format(MOMENT_FORMAT);
|
const DEFAULT_UNTIL = moment().startOf('day').format(MOMENT_FORMAT);
|
||||||
const INVALID_DATE_MESSAGE = 'Invalid date';
|
|
||||||
const SEPARATOR = ' : ';
|
const SEPARATOR = ' : ';
|
||||||
const FREEFORM_TOOLTIP = t(
|
const FREEFORM_TOOLTIP = t(
|
||||||
'Superset supports smart date parsing. Strings like `last sunday` or ' +
|
'Superset supports smart date parsing. Strings like `last sunday` or ' +
|
||||||
|
@ -68,19 +68,60 @@ const defaultProps = {
|
||||||
value: 'Last week',
|
value: 'Last week',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isValidMoment(s) {
|
||||||
function isFreeform(s) {
|
|
||||||
/* Moment sometimes consider invalid dates as valid, eg, "10 years ago" gets
|
/* Moment sometimes consider invalid dates as valid, eg, "10 years ago" gets
|
||||||
* parsed as "Fri Jan 01 2010 00:00:00" local time. This function does a
|
* parsed as "Fri Jan 01 2010 00:00:00" local time. This function does a
|
||||||
* better check by comparing a string with a parse/format roundtrip.
|
* better check by comparing a string with a parse/format roundtrip.
|
||||||
*/
|
*/
|
||||||
return (s !== moment(s, MOMENT_FORMAT).format(MOMENT_FORMAT));
|
return (s === moment(s, MOMENT_FORMAT).format(MOMENT_FORMAT));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStateFromSeparator(value) {
|
||||||
|
const [since, until] = value.split(SEPARATOR, 2);
|
||||||
|
return { since, until, type: TYPES.CUSTOM_START_END };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStateFromCommonTimeFrame(value) {
|
||||||
|
const units = value.split(' ')[1] + 's';
|
||||||
|
return {
|
||||||
|
type: TYPES.DEFAULTS,
|
||||||
|
common: value,
|
||||||
|
since: moment().startOf('day').subtract(1, units).format(MOMENT_FORMAT),
|
||||||
|
until: moment().startOf('day').format(MOMENT_FORMAT),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStateFromCustomRange(value) {
|
||||||
|
const [rel, num, grain] = value.split(' ', 3);
|
||||||
|
let since;
|
||||||
|
let until;
|
||||||
|
if (rel === RELATIVE_TIME_OPTIONS.LAST) {
|
||||||
|
until = moment().startOf('day').format(MOMENT_FORMAT);
|
||||||
|
since = moment()
|
||||||
|
.startOf('day')
|
||||||
|
.subtract(num, grain)
|
||||||
|
.format(MOMENT_FORMAT);
|
||||||
|
} else {
|
||||||
|
until = moment()
|
||||||
|
.startOf('day')
|
||||||
|
.add(num, grain)
|
||||||
|
.format(MOMENT_FORMAT);
|
||||||
|
since = moment().startOf('day').format(MOMENT_FORMAT);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: TYPES.CUSTOM_RANGE,
|
||||||
|
common: null,
|
||||||
|
rel,
|
||||||
|
num,
|
||||||
|
grain,
|
||||||
|
since,
|
||||||
|
until,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DateFilterControl extends React.Component {
|
export default class DateFilterControl extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const value = props.value || defaultProps.value;
|
|
||||||
this.state = {
|
this.state = {
|
||||||
type: TYPES.DEFAULTS,
|
type: TYPES.DEFAULTS,
|
||||||
|
|
||||||
|
@ -95,29 +136,36 @@ export default class DateFilterControl extends React.Component {
|
||||||
// distinct start/end values, either ISO or freeform
|
// distinct start/end values, either ISO or freeform
|
||||||
since: DEFAULT_SINCE,
|
since: DEFAULT_SINCE,
|
||||||
until: DEFAULT_UNTIL,
|
until: DEFAULT_UNTIL,
|
||||||
freeformInputs: {},
|
|
||||||
|
// react-datetime has a `closeOnSelect` prop, but it's buggy... so we
|
||||||
|
// handle the calendar visibility here ourselves
|
||||||
|
showSinceCalendar: false,
|
||||||
|
showUntilCalendar: false,
|
||||||
|
sinceViewMode: 'days',
|
||||||
|
untilViewMode: 'days',
|
||||||
};
|
};
|
||||||
if (value.indexOf(SEPARATOR) >= 0) {
|
|
||||||
this.state.type = TYPES.CUSTOM_START_END;
|
|
||||||
[this.state.since, this.state.until] = value.split(SEPARATOR, 2);
|
|
||||||
} else {
|
|
||||||
this.state.type = TYPES.DEFAULTS;
|
|
||||||
if (COMMON_TIME_FRAMES.indexOf(value) >= 0) {
|
|
||||||
this.state.common = value;
|
|
||||||
} else {
|
|
||||||
this.state.common = null;
|
|
||||||
[this.state.rel, this.state.num, this.state.grain] = value.split(' ', 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.state.freeformInputs.since = isFreeform(this.state.since);
|
|
||||||
this.state.freeformInputs.until = isFreeform(this.state.until);
|
|
||||||
|
|
||||||
// We need direct access to the state of the `DateTimeField` component
|
|
||||||
this.dateTimeFieldRefs = {};
|
|
||||||
|
|
||||||
|
this.close = this.close.bind(this);
|
||||||
this.handleClick = this.handleClick.bind(this);
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
this.isValidSince = this.isValidSince.bind(this);
|
||||||
|
this.isValidUntil = this.isValidUntil.bind(this);
|
||||||
|
this.onEnter = this.onEnter.bind(this);
|
||||||
|
this.renderInput = this.renderInput.bind(this);
|
||||||
|
this.setCustomRange = this.setCustomRange.bind(this);
|
||||||
|
this.setCustomStartEnd = this.setCustomStartEnd.bind(this);
|
||||||
|
this.setTypeCustomRange = this.setTypeCustomRange.bind(this);
|
||||||
|
this.setTypeCustomStartEnd = this.setTypeCustomStartEnd.bind(this);
|
||||||
|
this.toggleCalendar = this.toggleCalendar.bind(this);
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const value = this.props.value;
|
||||||
|
if (value.indexOf(SEPARATOR) >= 0) {
|
||||||
|
this.state = { ...this.state, ...getStateFromSeparator(value) };
|
||||||
|
} else if (COMMON_TIME_FRAMES.indexOf(value) >= 0) {
|
||||||
|
this.state = { ...this.state, ...getStateFromCommonTimeFrame(value) };
|
||||||
|
} else {
|
||||||
|
this.state = { ...this.state, ...getStateFromCustomRange(value) };
|
||||||
|
}
|
||||||
document.addEventListener('click', this.handleClick);
|
document.addEventListener('click', this.handleClick);
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -128,66 +176,37 @@ export default class DateFilterControl extends React.Component {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDefaults(timeFrame) {
|
|
||||||
const nextState = {
|
|
||||||
type: TYPES.DEFAULTS,
|
|
||||||
common: timeFrame,
|
|
||||||
until: moment().startOf('day').format(MOMENT_FORMAT),
|
|
||||||
};
|
|
||||||
const units = timeFrame.split(' ')[1] + 's';
|
|
||||||
nextState.since = moment().startOf('day').subtract(1, units).format(MOMENT_FORMAT);
|
|
||||||
this.setState(nextState, this.updateRefs);
|
|
||||||
}
|
|
||||||
setCustomRange(key, value) {
|
setCustomRange(key, value) {
|
||||||
const nextState = { ...this.state, type: TYPES.CUSTOM_RANGE };
|
const updatedState = { ...this.state, [key]: value };
|
||||||
if (key !== undefined && value !== undefined) {
|
const combinedValue = [updatedState.rel, updatedState.num, updatedState.grain].join(' ');
|
||||||
nextState[key] = value;
|
this.setState(getStateFromCustomRange(combinedValue));
|
||||||
}
|
|
||||||
if (nextState.rel === RELATIVE_TIME_OPTIONS.LAST) {
|
|
||||||
nextState.until = moment().startOf('day').format(MOMENT_FORMAT);
|
|
||||||
nextState.since = moment()
|
|
||||||
.startOf('day')
|
|
||||||
.subtract(nextState.num, nextState.grain)
|
|
||||||
.format(MOMENT_FORMAT);
|
|
||||||
} else {
|
|
||||||
nextState.until = moment()
|
|
||||||
.startOf('day')
|
|
||||||
.add(nextState.num, nextState.grain)
|
|
||||||
.format(MOMENT_FORMAT);
|
|
||||||
nextState.since = moment().startOf('day').format(MOMENT_FORMAT);
|
|
||||||
}
|
|
||||||
this.setState(nextState, this.updateRefs);
|
|
||||||
}
|
}
|
||||||
setCustomStartEnd(key, value) {
|
setCustomStartEnd(key, value) {
|
||||||
const nextState = {
|
const closeCalendar = (
|
||||||
|
(key === 'since' && this.state.sinceViewMode === 'days') ||
|
||||||
|
(key === 'until' && this.state.untilViewMode === 'days')
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
type: TYPES.CUSTOM_START_END,
|
type: TYPES.CUSTOM_START_END,
|
||||||
freeformInputs: { ...this.state.freeformInputs },
|
[key]: typeof value === 'string' ? value : value.format(MOMENT_FORMAT),
|
||||||
};
|
showSinceCalendar: this.state.showSinceCalendar && !closeCalendar,
|
||||||
if (value === INVALID_DATE_MESSAGE) {
|
showUntilCalendar: this.state.showUntilCalendar && !closeCalendar,
|
||||||
// the DateTimeField component will return `Invalid date` for freeform
|
sinceViewMode: closeCalendar ? 'days' : this.state.sinceViewMode,
|
||||||
// text, so we need to cheat and steal the value from the state
|
untilViewMode: closeCalendar ? 'days' : this.state.untilViewMode,
|
||||||
const freeformValue = this.dateTimeFieldRefs[key].state.inputValue;
|
});
|
||||||
nextState.freeformInputs[key] = true;
|
}
|
||||||
nextState[key] = freeformValue;
|
setTypeCustomRange() {
|
||||||
} else {
|
this.setState({ type: TYPES.CUSTOM_RANGE });
|
||||||
nextState.freeformInputs[key] = false;
|
}
|
||||||
nextState[key] = value;
|
setTypeCustomStartEnd() {
|
||||||
}
|
this.setState({ type: TYPES.CUSTOM_START_END });
|
||||||
this.setState(nextState, this.updateRefs);
|
|
||||||
}
|
}
|
||||||
handleClick(e) {
|
handleClick(e) {
|
||||||
// switch to `TYPES.CUSTOM_START_END` when the calendar is clicked
|
// switch to `TYPES.CUSTOM_START_END` when the calendar is clicked
|
||||||
if (this.startEndSectionRef && this.startEndSectionRef.contains(e.target)) {
|
if (this.startEndSectionRef && this.startEndSectionRef.contains(e.target)) {
|
||||||
this.setState({ type: TYPES.CUSTOM_START_END });
|
this.setTypeCustomStartEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateRefs() {
|
|
||||||
/* This is required because the <DateTimeField> component does not accept
|
|
||||||
* freeform dates as props, since they can't be parsed by `moment`.
|
|
||||||
*/
|
|
||||||
this.dateTimeFieldRefs.since.setState({ inputValue: this.state.since });
|
|
||||||
this.dateTimeFieldRefs.until.setState({ inputValue: this.state.until });
|
|
||||||
}
|
|
||||||
close() {
|
close() {
|
||||||
let val;
|
let val;
|
||||||
if (this.state.type === TYPES.DEFAULTS) {
|
if (this.state.type === TYPES.DEFAULTS) {
|
||||||
|
@ -199,11 +218,53 @@ export default class DateFilterControl extends React.Component {
|
||||||
}
|
}
|
||||||
this.props.onChange(val);
|
this.props.onChange(val);
|
||||||
this.refs.trigger.hide();
|
this.refs.trigger.hide();
|
||||||
|
this.setState({ showSinceCalendar: false, showUntilCalendar: false });
|
||||||
|
}
|
||||||
|
isValidSince(date) {
|
||||||
|
return (!isValidMoment(this.state.until) || date <= moment(this.state.until, MOMENT_FORMAT));
|
||||||
|
}
|
||||||
|
isValidUntil(date) {
|
||||||
|
return (!isValidMoment(this.state.since) || date >= moment(this.state.since, MOMENT_FORMAT));
|
||||||
|
}
|
||||||
|
toggleCalendar(key) {
|
||||||
|
const nextState = {};
|
||||||
|
if (key === 'showSinceCalendar') {
|
||||||
|
nextState.showSinceCalendar = !this.state.showSinceCalendar;
|
||||||
|
if (!this.state.showSinceCalendar) {
|
||||||
|
nextState.showUntilCalendar = false;
|
||||||
|
}
|
||||||
|
} else if (key === 'showUntilCalendar') {
|
||||||
|
nextState.showUntilCalendar = !this.state.showUntilCalendar;
|
||||||
|
if (!this.state.showUntilCalendar) {
|
||||||
|
nextState.showSinceCalendar = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState(nextState);
|
||||||
|
}
|
||||||
|
renderInput(props, key) {
|
||||||
|
return (
|
||||||
|
<FormGroup>
|
||||||
|
<InputGroup>
|
||||||
|
<FormControl
|
||||||
|
{...props}
|
||||||
|
type="text"
|
||||||
|
onKeyPress={this.onEnter}
|
||||||
|
onFocus={this.setTypeCustomStartEnd}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
<InputGroup.Button onClick={() => this.toggleCalendar(key)}>
|
||||||
|
<Button>
|
||||||
|
<Glyphicon glyph="calendar" style={{ padding: 3 }} />
|
||||||
|
</Button>
|
||||||
|
</InputGroup.Button>
|
||||||
|
</InputGroup>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
renderPopover() {
|
renderPopover() {
|
||||||
const grainOptions = TIME_GRAIN_OPTIONS.map(grain => (
|
const grainOptions = TIME_GRAIN_OPTIONS.map(grain => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onSelect={this.setCustomRange.bind(this, 'grain')}
|
onSelect={value => this.setCustomRange('grain', value)}
|
||||||
key={grain}
|
key={grain}
|
||||||
eventKey={grain}
|
eventKey={grain}
|
||||||
active={grain === this.state.grain}
|
active={grain === this.state.grain}
|
||||||
|
@ -215,7 +276,7 @@ export default class DateFilterControl extends React.Component {
|
||||||
<Radio
|
<Radio
|
||||||
key={timeFrame.replace(' ', '').toLowerCase()}
|
key={timeFrame.replace(' ', '').toLowerCase()}
|
||||||
checked={this.state.common === timeFrame}
|
checked={this.state.common === timeFrame}
|
||||||
onChange={this.setDefaults.bind(this, timeFrame)}
|
onChange={() => this.setState(getStateFromCommonTimeFrame(timeFrame))}
|
||||||
>
|
>
|
||||||
{timeFrame}
|
{timeFrame}
|
||||||
</Radio>
|
</Radio>
|
||||||
|
@ -236,7 +297,7 @@ export default class DateFilterControl extends React.Component {
|
||||||
<PopoverSection
|
<PopoverSection
|
||||||
title="Relative to today"
|
title="Relative to today"
|
||||||
isSelected={this.state.type === TYPES.CUSTOM_RANGE}
|
isSelected={this.state.type === TYPES.CUSTOM_RANGE}
|
||||||
onSelect={this.setCustomRange.bind(this)}
|
onSelect={this.setTypeCustomRange}
|
||||||
>
|
>
|
||||||
<div className="clearfix centered" style={{ marginTop: '12px' }}>
|
<div className="clearfix centered" style={{ marginTop: '12px' }}>
|
||||||
<div style={{ width: '60px', marginTop: '-4px' }} className="input-inline">
|
<div style={{ width: '60px', marginTop: '-4px' }} className="input-inline">
|
||||||
|
@ -245,17 +306,17 @@ export default class DateFilterControl extends React.Component {
|
||||||
componentClass={InputGroup.Button}
|
componentClass={InputGroup.Button}
|
||||||
id="input-dropdown-rel"
|
id="input-dropdown-rel"
|
||||||
title={this.state.rel}
|
title={this.state.rel}
|
||||||
onFocus={this.setCustomRange.bind(this)}
|
onFocus={this.setTypeCustomRange}
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onSelect={this.setCustomRange.bind(this, 'rel')}
|
onSelect={value => this.setCustomRange('rel', value)}
|
||||||
key={RELATIVE_TIME_OPTIONS.LAST}
|
key={RELATIVE_TIME_OPTIONS.LAST}
|
||||||
eventKey={RELATIVE_TIME_OPTIONS.LAST}
|
eventKey={RELATIVE_TIME_OPTIONS.LAST}
|
||||||
active={this.state.rel === RELATIVE_TIME_OPTIONS.LAST}
|
active={this.state.rel === RELATIVE_TIME_OPTIONS.LAST}
|
||||||
>Last
|
>Last
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onSelect={this.setCustomRange.bind(this, 'rel')}
|
onSelect={value => this.setCustomRange('rel', value)}
|
||||||
key={RELATIVE_TIME_OPTIONS.NEXT}
|
key={RELATIVE_TIME_OPTIONS.NEXT}
|
||||||
eventKey={RELATIVE_TIME_OPTIONS.NEXT}
|
eventKey={RELATIVE_TIME_OPTIONS.NEXT}
|
||||||
active={this.state.rel === RELATIVE_TIME_OPTIONS.NEXT}
|
active={this.state.rel === RELATIVE_TIME_OPTIONS.NEXT}
|
||||||
|
@ -267,11 +328,9 @@ export default class DateFilterControl extends React.Component {
|
||||||
<FormControl
|
<FormControl
|
||||||
bsSize="small"
|
bsSize="small"
|
||||||
type="text"
|
type="text"
|
||||||
onChange={event => (
|
onChange={event => this.setCustomRange('num', event.target.value)}
|
||||||
this.setCustomRange.call(this, 'num', event.target.value)
|
onFocus={this.setTypeCustomRange}
|
||||||
)}
|
onKeyPress={this.onEnter}
|
||||||
onFocus={this.setCustomRange.bind(this)}
|
|
||||||
onKeyPress={this.onEnter.bind(this)}
|
|
||||||
value={this.state.num}
|
value={this.state.num}
|
||||||
style={{ height: '30px' }}
|
style={{ height: '30px' }}
|
||||||
/>
|
/>
|
||||||
|
@ -282,7 +341,7 @@ export default class DateFilterControl extends React.Component {
|
||||||
componentClass={InputGroup.Button}
|
componentClass={InputGroup.Button}
|
||||||
id="input-dropdown-grain"
|
id="input-dropdown-grain"
|
||||||
title={this.state.grain}
|
title={this.state.grain}
|
||||||
onFocus={this.setCustomRange.bind(this)}
|
onFocus={this.setTypeCustomRange}
|
||||||
>
|
>
|
||||||
{grainOptions}
|
{grainOptions}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
@ -292,48 +351,37 @@ export default class DateFilterControl extends React.Component {
|
||||||
<PopoverSection
|
<PopoverSection
|
||||||
title="Start / end"
|
title="Start / end"
|
||||||
isSelected={this.state.type === TYPES.CUSTOM_START_END}
|
isSelected={this.state.type === TYPES.CUSTOM_START_END}
|
||||||
onSelect={this.setCustomStartEnd.bind(this)}
|
onSelect={this.setTypeCustomStartEnd}
|
||||||
info={FREEFORM_TOOLTIP}
|
info={FREEFORM_TOOLTIP}
|
||||||
>
|
>
|
||||||
<div ref={(ref) => { this.startEndSectionRef = ref; }}>
|
<div ref={(ref) => { this.startEndSectionRef = ref; }}>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<div style={{ margin: '5px 0' }}>
|
<div style={{ margin: '5px 0' }}>
|
||||||
<DateTimeField
|
<Datetime
|
||||||
ref={(ref) => { this.dateTimeFieldRefs.since = ref; }}
|
value={this.state.since}
|
||||||
dateTime={
|
defaultValue={this.state.since}
|
||||||
this.state.freeformInputs.since ?
|
viewDate={this.state.since}
|
||||||
DEFAULT_SINCE :
|
onChange={value => this.setCustomStartEnd('since', value)}
|
||||||
this.state.since
|
isValidDate={this.isValidSince}
|
||||||
}
|
onClick={this.setTypeCustomStartEnd}
|
||||||
defaultText={this.state.since}
|
renderInput={props => this.renderInput(props, 'showSinceCalendar')}
|
||||||
onChange={this.setCustomStartEnd.bind(this, 'since')}
|
open={this.state.showSinceCalendar}
|
||||||
maxDate={moment(this.state.until, MOMENT_FORMAT)}
|
viewMode={this.state.sinceViewMode}
|
||||||
format={MOMENT_FORMAT}
|
onViewModeChange={sinceViewMode => this.setState({ sinceViewMode })}
|
||||||
inputFormat={MOMENT_FORMAT}
|
|
||||||
onClick={this.setCustomStartEnd.bind(this)}
|
|
||||||
inputProps={{
|
|
||||||
onKeyPress: this.onEnter.bind(this),
|
|
||||||
onFocus: this.setCustomStartEnd.bind(this),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ margin: '5px 0' }}>
|
<div style={{ margin: '5px 0' }}>
|
||||||
<DateTimeField
|
<Datetime
|
||||||
ref={(ref) => { this.dateTimeFieldRefs.until = ref; }}
|
value={this.state.until}
|
||||||
dateTime={
|
defaultValue={this.state.until}
|
||||||
this.state.freeformInputs.until ?
|
viewDate={this.state.until}
|
||||||
DEFAULT_UNTIL :
|
onChange={value => this.setCustomStartEnd('until', value)}
|
||||||
this.state.until
|
isValidDate={this.isValidUntil}
|
||||||
}
|
onClick={this.setTypeCustomStartEnd}
|
||||||
defaultText={this.state.until}
|
renderInput={props => this.renderInput(props, 'showUntilCalendar')}
|
||||||
onChange={this.setCustomStartEnd.bind(this, 'until')}
|
open={this.state.showUntilCalendar}
|
||||||
minDate={moment(this.state.since, MOMENT_FORMAT).add(1, 'days')}
|
viewMode={this.state.untilViewMode}
|
||||||
format={MOMENT_FORMAT}
|
onViewModeChange={untilViewMode => this.setState({ untilViewMode })}
|
||||||
inputFormat={MOMENT_FORMAT}
|
|
||||||
inputProps={{
|
|
||||||
onKeyPress: this.onEnter.bind(this),
|
|
||||||
onFocus: this.setCustomStartEnd.bind(this),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
@ -347,7 +395,7 @@ export default class DateFilterControl extends React.Component {
|
||||||
bsSize="small"
|
bsSize="small"
|
||||||
className="float-right ok"
|
className="float-right ok"
|
||||||
bsStyle="primary"
|
bsStyle="primary"
|
||||||
onClick={this.close.bind(this)}
|
onClick={this.close}
|
||||||
>
|
>
|
||||||
Ok
|
Ok
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -7,6 +7,7 @@ const propTypes = {
|
||||||
value: PropTypes.oneOfType([
|
value: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.number,
|
PropTypes.number,
|
||||||
|
PropTypes.object,
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2200,12 +2200,6 @@ babel-register@^6.24.1, babel-register@^6.26.0, babel-register@^6.9.0:
|
||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
source-map-support "^0.4.15"
|
source-map-support "^0.4.15"
|
||||||
|
|
||||||
babel-runtime@^5.6.18:
|
|
||||||
version "5.8.38"
|
|
||||||
resolved "http://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19"
|
|
||||||
dependencies:
|
|
||||||
core-js "^1.0.0"
|
|
||||||
|
|
||||||
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
|
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
||||||
|
@ -8107,7 +8101,7 @@ moment-timezone@0.5.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
moment ">= 2.6.0"
|
moment ">= 2.6.0"
|
||||||
|
|
||||||
"moment@>= 2.6.0", moment@^2.20.1, moment@^2.8.2:
|
"moment@>= 2.6.0", moment@^2.20.1:
|
||||||
version "2.22.2"
|
version "2.22.2"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
||||||
|
|
||||||
|
@ -10009,14 +10003,6 @@ react-addons-test-utils@^15.6.2:
|
||||||
version "15.6.2"
|
version "15.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz#c12b6efdc2247c10da7b8770d185080a7b047156"
|
resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz#c12b6efdc2247c10da7b8770d185080a7b047156"
|
||||||
|
|
||||||
react-bootstrap-datetimepicker@0.0.22:
|
|
||||||
version "0.0.22"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-bootstrap-datetimepicker/-/react-bootstrap-datetimepicker-0.0.22.tgz#07e448d993157d049ad0876d0f9a3c9c5029d9c5"
|
|
||||||
dependencies:
|
|
||||||
babel-runtime "^5.6.18"
|
|
||||||
classnames "^2.1.2"
|
|
||||||
moment "^2.8.2"
|
|
||||||
|
|
||||||
react-bootstrap-dialog@^0.10.0:
|
react-bootstrap-dialog@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-bootstrap-dialog/-/react-bootstrap-dialog-0.10.0.tgz#fca5c84804ea2b6debe3833c6d4b7480bcff0175"
|
resolved "https://registry.yarnpkg.com/react-bootstrap-dialog/-/react-bootstrap-dialog-0.10.0.tgz#fca5c84804ea2b6debe3833c6d4b7480bcff0175"
|
||||||
|
@ -10062,9 +10048,9 @@ react-color@^2.13.8:
|
||||||
reactcss "^1.2.0"
|
reactcss "^1.2.0"
|
||||||
tinycolor2 "^1.4.1"
|
tinycolor2 "^1.4.1"
|
||||||
|
|
||||||
react-datetime@2.14.0:
|
react-datetime@^2.14.0:
|
||||||
version "2.14.0"
|
version "2.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-datetime/-/react-datetime-2.14.0.tgz#c7859c5b765275d7980f1cca27c03a727ff9ccef"
|
resolved "https://registry.yarnpkg.com/react-datetime/-/react-datetime-2.15.0.tgz#a8f7da6c58b6b45dbeea32d4e8485db17614e12c"
|
||||||
dependencies:
|
dependencies:
|
||||||
create-react-class "^15.5.2"
|
create-react-class "^15.5.2"
|
||||||
object-assign "^3.0.0"
|
object-assign "^3.0.0"
|
||||||
|
|
Loading…
Reference in New Issue