/* Copyright (C) 2017 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ // @flow import React from 'react' import ReactDOM from 'react-dom' import { observer } from 'mobx-react' import styled, { injectGlobal } from 'styled-components' import Datetime from 'react-datetime' import moment from 'moment' import DropdownButton from '../../atoms/DropdownButton' import DomUtils from '../../../utils/DomUtils' import DateUtils from '../../../utils/DateUtils' import StyleProps from '../../styleUtils/StyleProps' import style from './style.js' require('moment/locale/en-gb') injectGlobal`${style}` const Wrapper = styled.div` width: ${StyleProps.inputSizes.regular.width}px; ` const DropdownButtonStyled = styled(DropdownButton)` font-size: 12px; ` const Portal = styled.div` position: absolute; z-index: 10; &.hideTip { .rdtPicker:after { content: none; } } ` const DatetimeStyled = styled(Datetime)`` type Props = { value: ?Date, onChange: (date: Date) => void, isValidDate?: (currentDate: Date, selectedDate: Date) => boolean, timezone: 'utc' | 'local', useBold?: boolean, } type State = { showPicker: boolean, date: ?moment$Moment, } @observer class DatetimePicker extends React.Component { itemMouseDown: boolean portalRef: HTMLElement buttonRef: HTMLElement scrollableParent: HTMLElement constructor() { super() this.state = { showPicker: false, date: null, } // $FlowIssue this.handlePageClick = this.handlePageClick.bind(this) // $FlowIssue this.handleScroll = this.handleScroll.bind(this) } componentWillMount() { if (this.props.value) { this.setState({ date: moment(this.props.value) }) } } componentDidMount() { window.addEventListener('mousedown', this.handlePageClick, false) if (this.buttonRef) { this.scrollableParent = DomUtils.getScrollableParent(this.buttonRef) this.scrollableParent.addEventListener('scroll', this.handleScroll) } } componentDidUpdate() { this.setPortalPosition() } componentWillUnmount() { window.removeEventListener('mousedown', this.handlePageClick, false) this.scrollableParent.removeEventListener('scroll', this.handleScroll, false) } setPortalPosition() { if (!this.portalRef || !this.buttonRef) { return } const buttonRect = this.buttonRef.getBoundingClientRect() const leftOffset = (buttonRect.left - (this.portalRef.offsetWidth - buttonRect.width)) + 10 const tipHeight = 12 let topOffset = buttonRect.top + this.buttonRef.offsetHeight + tipHeight let listHeight = this.portalRef.offsetHeight if (topOffset + listHeight > window.innerHeight) { topOffset = window.innerHeight - listHeight - 16 this.portalRef.classList.add('hideTip') } else { this.portalRef.classList.remove('hideTip') } this.portalRef.style.top = `${topOffset + window.pageYOffset}px` this.portalRef.style.left = `${leftOffset + window.pageXOffset}px` } isValidDate(currentDate: Date, selectedDate: Date): boolean { if (!this.props.isValidDate) { return true } return this.props.isValidDate(currentDate, selectedDate) } handleScroll() { if (this.buttonRef) { if (DomUtils.isElementInViewport(this.buttonRef, this.scrollableParent)) { this.setPortalPosition() } else if (this.state.showPicker) { this.setState({ showPicker: false }) } } } handlePageClick(e: Event) { let path = DomUtils.getEventPath(e) if (!this.itemMouseDown && !path.find(n => n.className === 'rdtPicker')) { this.dispatchChange() this.setState({ showPicker: false }) } } handleDropdownClick() { this.dispatchChange() this.setState({ showPicker: !this.state.showPicker }) } handleChange(newDate: Date) { let date = moment(newDate) if (this.props.timezone === 'utc') { date = DateUtils.getLocalTime(newDate) } this.setState({ date }) } dispatchChange() { if ( this.state.date && this.state.showPicker && this.state.date.toDate().getTime() !== (this.props.value && this.props.value.getTime()) ) { this.props.onChange(this.state.date.toDate()) } } renderDateTimePicker(timezoneDate: ?moment$Moment) { if (!this.state.showPicker) { return null } let body: any = document.body return ReactDOM.createPortal(( { this.portalRef = e }}> { this.handleChange(date) }} dateFormat="DD/MM/YYYY" timeFormat="hh:mm A" locale="en-gb" isValidDate={(currentDate, selectedDate) => this.isValidDate(currentDate, selectedDate)} /> ), body) } render() { let timezoneDate = this.state.date if (this.props.timezone === 'utc' && timezoneDate) { timezoneDate = DateUtils.getUtcTime(timezoneDate) } return ( { this.buttonRef = e }} data-test-id="datetimePicker-dropdownButton" width={207} value={(timezoneDate && moment(timezoneDate).format('DD/MM/YYYY hh:mm A')) || '-'} centered useBold={this.props.useBold} onClick={() => { this.handleDropdownClick() }} onMouseDown={() => { this.itemMouseDown = true }} onMouseUp={() => { this.itemMouseDown = false }} /> {this.renderDateTimePicker(timezoneDate)} ) } } export default DatetimePicker