import * as React from 'react'; import FormComponent from '../FormComponent'; import classNames from 'classnames'; import Input from '../Input'; import {isEqual, isEmpty} from 'lodash'; import {getId} from '../function'; import {DayPickerRangeController, DayPickerSingleDateController} from 'react-dates'; import 'react-dates/initialize'; import 'react-dates/lib/css/_datepicker.css'; import * as moment from 'moment'; import OutsideClickHandler from 'react-outside-click-handler'; export interface DisableDays { (date?: moment.Moment): boolean } export interface Time { caption: string, value: any, noon?: string } export interface DatePickerProps { rangePicker?: boolean, dateStart?: any, dateEnd?: any, color?: string, errorMessage?: string, numberOfMonths?: number, date?: any, weekendDisabled?: boolean, dateFormat: string, minDate?: any maxDate?: any, disableDays?: DisableDays, showTime?: boolean, timeStart?: Time, timeEnd?: Time, time?: Time, timeAs24?: boolean, timeIncludeSeconds?: boolean, onChange?: Function, disabled?: boolean, readOnly?: boolean, timeValidate?: boolean, uiType?: string, inputOption?: any, validate?: any[], datePickerFocus?: any; } export interface DatePickerState { uuid: string, rangePicker: boolean, dateStart?: any, dateEnd?: any, customStyle: any, date: any, dateFormat: string, datePickerFocus: any, minDate: any, maxDate: any, timeStart: Time, timeEnd: Time, time: Time, timeFormat: string, timeIncludeSeconds: boolean, timeValidate: boolean, show: boolean, inputOption: any, dateInputMask: any, color: string, errorMessage?: string, numberOfMonths?: number, weekendDisabled?: boolean, disableDays?: DisableDays, showTime?: boolean, timeAs24?: boolean, onChange?: Function, disabled?: boolean, readOnly? : boolean, uiType?:string, validate?: any[] } const START_DATE = 'startDate'; const DEFAULT_DATE_FORMAT = 'YYYY.MM.DD'; // const END_DATE = 'endDate'; export default class DatePicker extends FormComponent { private div = React.createRef(); private selectedFromDatePicker: boolean = false; constructor(props: DatePickerProps) { super(props); const {time, timeStart, timeEnd, rangePicker } = props; const dateFormat = props.dateFormat || DEFAULT_DATE_FORMAT; let iOption = {}; if (props.inputOption && !isEmpty(props.inputOption)) { iOption = { editable: props.inputOption.editable || true, icon: props.inputOption.icon || null, label: props.inputOption.label || null, caption: props.inputOption.caption || '', size: props.inputOption.size || 'medium', customStyle: props.inputOption.customStyle || '', closeAfterSelect: props.inputOption.closeAfterSelect || true, infoText: props.inputOption.infoText || '', autoFocus: props.inputOption.autoFocus || false, noBorder: props.inputOption.noBorder || false, noRadius: props.inputOption.noRadius || false, } } const uuid = getId(null, this.defaultId); this.state = { rangePicker: false, dateInputMask: this.getInputMask(rangePicker ? rangePicker : false, dateFormat), customStyle: '', color: 'black', timeIncludeSeconds: false, // weekendDisabled: false, // doValidate: !props.disabled || true, show: false, timeValidate: false, uuid: uuid, ...props, dateFormat, inputOption: iOption, timeFormat: this.props.timeIncludeSeconds ? '00:00:00' : '00:00', time: !time ? { caption: 'Start Time', value: '12:00:00', noon: 'am' } : time, timeStart: !timeStart ? { caption: 'Start Time', value: '12:00:00', noon: 'am' } : timeStart, timeEnd: !timeEnd ? { caption: 'End Time', value: '12:00', noon: 'am' } : timeEnd, date: props.date ? moment(props.date, dateFormat) : null, minDate: props.minDate ? moment(props.minDate, dateFormat) : null, maxDate: props.maxDate ? moment(props.maxDate, dateFormat) : null, // For RangePicker datePickerFocus: props.rangePicker ? START_DATE : true, dateStart: props.dateStart ? moment(props.dateStart, dateFormat) : null, dateEnd: props.dateEnd ? moment(props.dateEnd, dateFormat) : null, } } componentDidMount() { if (this.state.inputOption.autoFocus) { this.openDatePicker() } this.validate(this.getInputValue()); this.updateOnChangeCallback() } getInputMask = (isRangePicker, dateFormat) => { return isRangePicker ? { mask: `${dateFormat} - ${dateFormat}`.replace(/[A-Z]/g, '0') } : { mask: dateFormat.replace(/[A-Z]/g, '0') } } getInputValue() { const {rangePicker, dateEnd, dateStart} = this.state; let x = this.getDateValue(); let date_:string = ''; let dateStart_:string = ''; let dateEnd_:string = ''; if (rangePicker) { if (dateStart && dateEnd) { dateEnd_ = x['dateEnd'].dateFormatted; dateStart_ = x['dateStart'].dateFormatted; date_ = dateStart_ && dateEnd_ ? dateStart_ +' - '+ dateEnd_ : '' } else { date_ = '' } } else { date_ = x['date'] ? x['date'].dateFormatted : '' } return date_ } isDatetoString(date:any) { return moment(date, this.state.dateFormat).format(this.state.dateFormat) } componentWillReceiveProps(nextProps:any) { if (!isEqual(this.isDatetoString(nextProps.date), this.isDatetoString(this.props.date))) { const newDate = moment(nextProps.date, this.state.dateFormat); if (newDate.isValid()) { this.setState({ date: newDate }) } else { this.setState({ date: null }) } } if (!isEqual(this.isDatetoString(nextProps.dateStart), this.isDatetoString(this.props.dateStart))) { const newDate = moment(nextProps.dateStart, this.state.dateFormat); if (newDate.isValid()) { this.setState({ dateStart: newDate }) } else { this.setState({ dateStart: null }) } } if (!isEqual(this.isDatetoString(nextProps.dateEnd), this.isDatetoString(this.props.dateEnd))) { const newDate = moment(nextProps.dateEnd, this.state.dateFormat); if (newDate.isValid()) { this.setState({ dateEnd: newDate }) } else { this.setState({ dateEnd: null }) } } if ( !isEqual(nextProps.dateFormat, this.props.dateFormat)) { if (typeof nextProps.dateFormat !== 'undefined') { this.setState({ dateFormat: nextProps.dateFormat, dateInputMask: this.getInputMask(nextProps.rangePicker, nextProps.dateFormat) }) } else { this.setState({ dateFormat: DEFAULT_DATE_FORMAT, dateInputMask: this.getInputMask(nextProps.rangePicker, DEFAULT_DATE_FORMAT) }) } } if (!isEqual(nextProps.datePickerFocus, this.props.datePickerFocus)) { this.setState({ datePickerFocus: nextProps.datePickerFocus }) } if (!isEqual(nextProps.rangePicker, this.props.rangePicker)) { this.setState({ rangePicker: nextProps.rangePicker, datePickerFocus: nextProps.rangePicker ? START_DATE : true, dateInputMask: this.getInputMask(nextProps.rangePicker, this.state.dateFormat) }); } if (!isEqual(this.isDatetoString(nextProps.minDate), this.isDatetoString(this.props.minDate))) { this.setState({ minDate: nextProps.minDate }) } if (!isEqual(this.isDatetoString(nextProps.maxDate), this.isDatetoString(this.props.maxDate))) { this.setState({ maxDate: nextProps.maxDate }) } if (!isEqual(nextProps.time, this.props.time) && nextProps.time) { this.setState({ time: nextProps.time }) } if (!isEqual(nextProps.timeEnd, this.props.timeEnd) && nextProps.timeEnd) { this.setState({ timeEnd: nextProps.timeEnd }) } if (!isEqual(nextProps.timeStart, this.props.timeStart) && nextProps.timeStart) { this.setState({ timeStart: nextProps.timeStart }) } if (!isEqual(nextProps.inputOption, this.props.inputOption)) { this.setState({ inputOption: nextProps.inputOption }) } if (!isEqual(nextProps.weekendDisabled, this.props.weekendDisabled)) { this.setState({ weekendDisabled: nextProps.weekendDisabled, show: false }) } if (!isEqual(nextProps.timeIncludeSeconds, this.props.timeIncludeSeconds)) { this.setState({ timeIncludeSeconds: nextProps.timeIncludeSeconds, timeFormat: nextProps.timeIncludeSeconds ? '00:00:00' : '00:00', show: false }) } if (!isEqual(nextProps.timeValidate, this.props.timeValidate)) { this.setState({ timeValidate: true }) } } isAfterMinDate = (date: moment.Moment) => { const {minDate} = this.state; return date.isSameOrAfter(minDate, 'day'); }; isBeforeMaxDate = (date: moment.Moment) => { const {maxDate} = this.state; return date.isSameOrBefore(maxDate, 'day'); }; isWeekEnd = (date: moment.Moment) => { const weekDay = date.day(); return weekDay === 6 || weekDay === 0 }; dateOutsideRules = (date: moment.Moment) => { const {minDate, maxDate} = this.state; const {weekendDisabled} = this.props; const checkMinDate = minDate ? !this.isAfterMinDate(date) : false; const checkMaxDate = maxDate ? !this.isBeforeMaxDate(date) : false; const checkWeekend = weekendDisabled ? this.isWeekEnd(date) : false; return checkMinDate || checkMaxDate || checkWeekend; }; updateOnChangeCallback = () => { const {onChange} = this.props; this.validate(this.getInputValue()); onChange && onChange(this.getDateValue()); }; getDateValue = () => { const {date, dateStart, dateEnd, time, timeStart, timeEnd, rangePicker, dateFormat, timeIncludeSeconds} = this.state; const {showTime, timeAs24} = this.props; const fallbackValue = [0, 0, 0]; const timeArray = time.value ? time.value.split(':') : fallbackValue; const timeStartArray = time.value ? timeStart.value.split(':') : fallbackValue; const timeEndArray = time.value? timeEnd.value.split(':') : fallbackValue; let value:any = null; if (rangePicker) { if (dateStart && dateEnd) { value = { dateStart: { dateAsMoment: dateStart, dateFormatted: dateStart.format(dateFormat), dateAsDate: dateStart.toDate() }, timeStart: { timeAsFormatted: timeStart.value, timeAsObject: { hours: parseInt(timeStartArray[0] || 0), minutes: parseInt(timeStartArray[1] || 0), seconds: parseInt(timeStartArray[2] || 0) }, noon: timeStart.noon }, dateEnd: { dateAsMoment: dateEnd, dateFormatted: dateEnd.format(dateFormat), dateAsDate: dateEnd.toDate() }, timeEnd: { timeAsFormatted: timeEnd.value, timeAsObject: { hours: parseInt(timeEndArray[0] || 0), minutes: parseInt(timeEndArray[1] || 0), seconds: parseInt(timeEndArray[2] || 0) }, noon: timeEnd.noon } }; !timeIncludeSeconds ? delete value.timeStart.timeAsObject.seconds : null; !timeIncludeSeconds ? delete value.timeEnd.timeAsObject.seconds : null; !time.noon || timeAs24 ? delete value.timeStart.noon : null; !time.noon || timeAs24 ? delete value.timeEnd.noon : null; !showTime ? delete value.timeStart : null; !showTime ? delete value.timeEnd : null; } else { value = '' } return value; } else { if (date) { value = { date: { dateAsMoment: date, dateFormatted: date.format(dateFormat), dateAsDate: date.toDate() }, time: { timeAsFormatted: time.value, timeAsObject: { hours: parseInt(timeArray[0]), minutes: parseInt(timeArray[1]), seconds: parseInt(timeArray[2] || 0) }, noon: time.noon } } !timeIncludeSeconds ? delete value.time.timeAsObject.seconds : null; !time.noon || timeAs24 ? delete value.time.noon : null; !showTime ? delete value.time : null; } else { value = '' } return value; } }; onSingleDatePickerDateChange = (date:any) => { this.selectedFromDatePicker = true; const { uiType, inputOption } = this.props; this.setState({ date: date, show: uiType === 'input' && inputOption.closeAfterSelect ? !inputOption.closeAfterSelect : true }, () => { this.updateOnChangeCallback() }); }; onSingleDatePickerFocusChange = () => { this.selectedFromDatePicker = true; this.setState({ datePickerFocus: true }) }; onTimeChange = (value: any, stateKey: string, time12Data: any) => { const { timeFormat } = this.state; const temp = {}; if (value.length !== timeFormat.length) { return; } temp[stateKey] = { ...this.state[stateKey], value }; if (time12Data) { temp[stateKey].noon = time12Data.dropdown; } this.setState({ ...temp }, () => { this.updateOnChangeCallback(); }); }; renderTime = (time: Time, stateKey: string) => { const {timeFormat, rangePicker} = this.state; const {numberOfMonths, timeAs24, timeValidate, timeIncludeSeconds} = this.props; let dd:any = {}; if (!timeAs24) { dd = { dropdown: { data: [{id: 'pm', text: 'PM'}, {id: 'am', text: 'AM'}], selected: time.noon ? time.noon.toLowerCase() : 'am', displayField: 'text', displayValue: 'id' } } } let style:any = {}; if (rangePicker) { if (numberOfMonths && numberOfMonths > 1) { style = { width: 'calc(50% - 10px)' } } else { style = { flexGrow: 1, marginBottom: 20 } } } let timeV:any = {}; if (timeValidate) { timeV = { validate: ['required', timeAs24 ? 'time24' : 'time'] } } const hasSeconds: boolean = time.value.match(/:/g).length === 2; let timeValue = !timeIncludeSeconds ? (hasSeconds ? time.value.substr(0, time.value.lastIndexOf(':')) : time.value) : time.value; return (
{ this.onTimeChange(timeAs24 ? val : val.value, stateKey, timeAs24 ? null : val); }} {...timeV} mask={timeFormat} icon="fal fa-clock" value={timeValue} size='small' {...dd} />
) }; renderRangeDate() { const {datePickerFocus, timeStart, timeEnd, dateStart, dateEnd, customStyle} = this.state; const {color, uiType, numberOfMonths, disableDays, showTime, inputOption } = this.props; const uiClass = classNames(`krax-date ${customStyle || ''}krax-date-${color || 'black'}`, { 'focus': focus, 'error': !isEmpty(this.error()), 'input-modal': uiType === 'input' }); return { if (uiType !== 'input') return; if (this.div.current && !this.div.current['contains'](e.target)) this.closeDatePicker() }}>
{ this.setState({ dateStart: startDate, dateEnd: endDate }, () => { this.selectedFromDatePicker = true; if (startDate && endDate && datePickerFocus === 'endDate') { this.setState({ show: uiType === 'input' ? (typeof inputOption.closeAfterSelect !== 'undefined' ? !inputOption.closeAfterSelect : false) : true }, () => { this.updateOnChangeCallback() }) } }); }} focusedInput={datePickerFocus} startDate={dateStart} endDate={dateEnd} // daySize={20} onFocusChange={(focusedInput) => { this.setState({ datePickerFocus: !focusedInput ? START_DATE : focusedInput }); }} noBorder isOutsideRange={disableDays && disableDays || this.dateOutsideRules} renderCalendarInfo={showTime? () => { return (
1 ? 'row' : 'column', justifyContent: numberOfMonths && numberOfMonths > 1 ? 'space-between' : 'center' }}> {this.renderTime(timeStart, 'timeStart')} {this.renderTime(timeEnd, 'timeEnd')}
) } : () => ''} hideKeyboardShortcutsPanel /> {this.error() && uiType !== 'input' ?
{this.error()}
: null}
} renderSingleDate() { const {date, datePickerFocus, time, customStyle} = this.state; const {color, numberOfMonths, uiType, disableDays, showTime} = this.props; const uiClass = classNames(`krax-date ${customStyle || ''} krax-date-${color || 'black'}`, { 'focus': focus, 'error': !isEmpty(this.error()), 'input-modal': uiType === 'input', 'is-date-inline': uiType !== 'input' }); return { if (uiType !== 'input') return; if (this.div.current && this.div.current['contains'] && !this.div.current['contains'](e.target)) { this.closeDatePicker() } }}>
{ return (
{this.renderTime(time, 'time')}
) } : () => ''} /> {this.error() && uiType !== 'input' ?
{this.error()}
: null}
} renderInline() { const {rangePicker, disabled, readOnly} = this.props; return
{!rangePicker ? this.renderSingleDate() : this.renderRangeDate()}
} openDatePicker = () => { this.setState({ show: true }); }; closeDatePicker = () => { this.setState({ show: false }) }; onInputChange = (dateString:any) => { const {dateFormat, inputOption, rangePicker, date, dateInputMask} = this.state; if (this.selectedFromDatePicker) { this.selectedFromDatePicker = false; return; } if (dateString.length === dateInputMask.mask.length) { if (rangePicker) { const dateStart = moment(dateString.split(' - ')[0], dateFormat); const dateEnd = moment(dateString.split(' - ')[1], dateFormat); if (dateStart.isValid() && dateEnd.isValid() && !this.dateOutsideRules(dateStart) && !this.dateOutsideRules(dateEnd)) { this.setState({ dateStart: dateStart, dateEnd: dateEnd, show: !inputOption.closeAfterSelect }, () => this.updateOnChangeCallback()); } } else { const newDate = moment(dateString, dateFormat); if (newDate.isValid() && !this.dateOutsideRules(newDate) && newDate !== date) { this.setState({ date: newDate, show: !inputOption.closeAfterSelect }, () => { this.updateOnChangeCallback() }); } } } else if (!dateString.length) { this.setState({ date: null, dateStart: null, dateEnd: null, show: !inputOption.closeAfterSelect }, () => { this.updateOnChangeCallback() }); } }; getRangeInputValue() { const {dateStart, dateEnd, dateFormat} = this.state; if (dateStart && dateEnd) { return `${dateStart.format(dateFormat)} - ${dateEnd.format(dateFormat)}` } return ''; } renderInput() { const {show, uuid, date, inputOption, dateInputMask, dateFormat} = this.state; const {disabled, rangePicker} = this.props; return
{ if (!show || inputOption.autoFocus) { this.openDatePicker() } }} icon={inputOption.icon} label={inputOption.label} {...dateInputMask} value={rangePicker ? this.getRangeInputValue() : date ? moment(date, dateFormat).format(dateFormat) : '' } size={inputOption.size} customStyle={`${inputOption.customStyle} ${show ? 'is-bottom-radius-none' : ''}`} debounce={300} noBorder={inputOption.noBorder} noRadius={inputOption.noRadius} infoText={inputOption.infoText} customRender={() => !rangePicker ? (show ? this.renderSingleDate() : null) : (show ? this.renderRangeDate() : null)} inputDropdown={show} /> {/*{!rangePicker ? (show ? this.renderSingleDate() : null) : (show ? this.renderRangeDate() : null)}*/}
} renderDatePicker() { const {uiType} = this.props; if (uiType === 'input') { return this.renderInput(); } return this.renderInline() } public render() { return this.renderDatePicker() } }