/* eslint-disable react/no-array-index-key */
import React, { Component } from 'react';
import {
daysInMonth,
weekDayInMonth,
getDateForCalendar,
getMonth,
getDateForFilter,
getCountryNewDate,
} from '../utils/common';
import SVGComponent from './SVG/SVGComponent';
import { SVG_ICONS } from '../constants';
/**
* @module Calendar
*/
/**
* @typedef {object} props
* @property {string} className
* @property {boolean} sub
* @property {string} field
* @property {object} filter
* @property {object} defaultFilter
* @property {boolean} actions
* @property {Time} inactivePreviousDates Use when date need to be inactive
* before some date.
* @property {Function} changeFilter
*/
export default class Calendar extends Component {
constructor(props) {
super(props);
/**
* @member {object}
* @property {Time} selectedDate
* @property {boolean} isOpen
* @property {Time} date
*/
const defaultDate = props.filter[props.field];
const selectedDate = defaultDate
? getCountryNewDate(this.props.selectedTimezone, defaultDate)
: getCountryNewDate(this.props.selectedTimezone);
this.state = {
month: selectedDate,
selectedDate,
isOpen: false,
date: defaultDate ? getCountryNewDate(this.props.selectedTimezone, defaultDate) : null,
};
/**
* @member {Array}
*/
this.inactiveDatesBefore = [];
/**
* @member {Array}
*/
this.activeDates = [];
/**
* @member {Array}
*/
this.inactiveDatesAfter = [];
// COMPONENT WILL MOUNT
this.setDataForOneMonth(this.state.selectedDate);
}
/**
* Update state on clear filter
*
* @param {object} prevProps
* @returns {void}
*/
componentDidUpdate(prevProps) {
if (this.props.filter[this.props.field] !== prevProps.filter[prevProps.field]) {
const defaultDate = this.props.filter[this.props.field];
const selectedDate = defaultDate
? getCountryNewDate(this.props.selectedTimezone, defaultDate)
: getCountryNewDate(this.props.selectedTimezone);
this.setDataForOneMonth(selectedDate);
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
selectedDate,
isOpen: false,
date: defaultDate ? getCountryNewDate(this.props.selectedTimezone, defaultDate) : null,
month: selectedDate,
});
}
}
/**
* Toggle calendar
*
* @function
* @returns {void}
*/
toggleCalendarPopup = () => {
if (!this.state.isOpen) {
this.setState({
isOpen: true,
});
} else {
this.cancel();
}
};
/**
* Set previous month
*
* @function
* @returns {void}
*/
setPreviousMonth = () => {
const month = this.state.month;
if (this.props.inactivePreviousDates && this.props.inactivePreviousDates.getMonth() === month.getMonth()) {
return;
}
month.setMonth(month.getMonth() - 1, 1);
this.setDataForOneMonth(month);
this.setState({
month,
});
};
/**
* Set next month
*
* @function
* @returns {void}
*/
setNextMonth = () => {
const month = this.state.month;
month.setMonth(month.getMonth() + 1, 1);
this.setDataForOneMonth(month);
this.setState({
month,
});
};
/**
* Change day
*
* @function
* @param {number} day
* @returns {void}
*/
setDay = (day) => {
const date = this.state.selectedDate;
date.setFullYear(this.state.month.getFullYear(), this.state.month.getMonth(), day);
this.setState(
{
selectedDate: date,
month: new Date(date.getTime()),
},
() => {
if (!this.props.actions) {
this.applay('setDay');
}
}
);
};
/**
* Set default filter
*
* @function
* @returns {void}
*/
reset = () => {
let date;
if (this.props.defaultFilter && this.props.defaultFilter[this.props.field]) {
date = getCountryNewDate(this.props.selectedTimezone, this.props.defaultFilter[this.props.field]);
this.props.changeFilter(getDateForFilter(date), this.props.field);
}
const selectedDate = date
? getCountryNewDate(this.props.selectedTimezone, date.getTime())
: getCountryNewDate(this.props.selectedTimezone);
this.setState({
isOpen: false,
date,
selectedDate,
month: getCountryNewDate(this.props.selectedTimezone, selectedDate.getTime()),
});
};
/**
* Discard changes
*
* @function
* @returns {void}
*/
cancel = () => {
const selectedDate = this.state.date ? this.state.date : new Date();
this.setState({
isOpen: false,
selectedDate,
});
};
/**
* Change date
*
* @function
* @param {string} type
* @returns {void}
*/
applay = (type) => {
this.props.changeFilter(getDateForFilter(this.state.selectedDate), this.props.field);
this.setState({
isOpen: false,
date: this.state.selectedDate
? type && type === 'setDay'
? new Date(this.state.selectedDate.getTime())
: getCountryNewDate(this.props.selectedTimezone, this.state.selectedDate.getTime())
: null,
});
};
/**
* Calculate month view
*
* @function
* @param {Time} date
*/
setDataForOneMonth = (date) => {
const copiedDate = new Date(date.getTime());
let numOfDays = daysInMonth(copiedDate.getMonth() + 1, copiedDate.getFullYear());
const startWith = weekDayInMonth(copiedDate.getMonth(), copiedDate.getFullYear());
copiedDate.setFullYear(copiedDate.getFullYear(), copiedDate.getMonth() - 1, 1);
let numOfDaysPreviousMonth = daysInMonth(copiedDate.getMonth() + 1, copiedDate.getFullYear());
numOfDaysPreviousMonth -= startWith;
this.inactiveDatesBefore = Array(startWith)
.fill()
.map(() => {
numOfDaysPreviousMonth += 1;
return numOfDaysPreviousMonth;
});
let counter = 0;
if (this.props.inactivePreviousDates && this.props.inactivePreviousDates.getMonth() === date.getMonth()) {
const numOfInvalidDates = this.props.inactivePreviousDates.getDate() - 1;
Array(numOfInvalidDates)
.fill()
.map(() => {
counter += 1;
this.inactiveDatesBefore.push(counter);
});
numOfDays -= numOfInvalidDates;
}
this.activeDates = Array(numOfDays)
.fill()
.map(() => {
counter += 1;
return counter;
});
let i = 0;
this.inactiveDatesAfter = Array(42 - counter - startWith)
.fill()
.map(() => {
i += 1;
return i;
});
};
/**
* Parse date objects
*
* @function
* @returns {object}
*/
getCalendarData = () => {
const dateTime = new Date();
const fullDate = getDateForCalendar(dateTime);
let date = getDateForCalendar(this.state.date);
date = date.slice(0, -4) + date.slice(date.length - 2, date.length);
const month = getMonth(this.state.month);
const currentDay = this.state.month.getMonth() === dateTime.getMonth() ? this.state.selectedDate.getDate() : -1;
return {
currentDay,
date,
month,
fullDate,
};
};
/**
* Render
*
* @returns {view}
*/
render() {
const data = this.getCalendarData();
return (
<>
<div className={this.props.className}>
<div className="calendar__wrapper d-flex align-items-center" onClick={this.toggleCalendarPopup}>
<div className="calendar__input-date">
<div className="mr-2">{data.date}</div>
</div>
<i className={`icon-calendar ${this.props.sub && 'green'}`} onClick={this.toggleCalendarPopup} />
</div>
<div className={`calendar__popup ${this.state.isOpen && 'open'} ${this.props.sub && 'sub bg-grey'}`}>
<div className="p-15">
<div className="calendar__popup-heading">
<h3>{data.fullDate}</h3>
<SVGComponent
className={`icon-xxs calendar__popup-close${this.props.sub ? '-sub' : ''}`}
src={`${SVG_ICONS.utility}#close`}
onClick={this.toggleCalendarPopup}
/>
</div>
</div>
{/* calendar__popup-date */}
<div className="calendar__popup-row active">
<div>{data.month}</div>
<div className="ml-auto">
<SVGComponent
className="icon-xxs prev-icon"
src={`${SVG_ICONS.pagination}#arrow`}
onClick={this.setPreviousMonth}
/>
<SVGComponent
className="icon-xxs next-icon ml-2"
src={`${SVG_ICONS.pagination}#arrow`}
onClick={this.setNextMonth}
/>
</div>
</div>
<div className="calendar__popup-table">
<div className="calendar__popup-table-cell">S</div>
<div className="calendar__popup-table-cell">M</div>
<div className="calendar__popup-table-cell">T</div>
<div className="calendar__popup-table-cell">W</div>
<div className="calendar__popup-table-cell">T</div>
<div className="calendar__popup-table-cell">F</div>
<div className="calendar__popup-table-cell">S</div>
{this.inactiveDatesBefore.map((day, idx) => (
<div className="calendar__popup-table-cell date inactive" key={`inactiveBefore-${day}-${idx}`}>
{day}
</div>
))}
{this.activeDates.map((day, idx) => (
<div
key={`active-${day}-${idx}`}
className={`calendar__popup-table-cell date ${data.currentDay === day ? 'selected' : ''}`}
onClick={this.setDay.bind(this, day)}
>
{day}
</div>
))}
{this.inactiveDatesAfter.map((day, idx) => (
<div className="calendar__popup-table-cell date inactive" key={`inactiveAfter-${day}-${idx}`}>
{day}
</div>
))}
</div>
{this.props.actions && (
<div className="p-15">
<div className="calendar__popup-button">
<button type="button" className="btn btn-secondary mr-auto" onClick={this.reset}>
Reset
</button>
<button type="button" className="btn btn-secondary" onClick={this.cancel}>
Cancel
</button>
<button type="button" onClick={this.applay} className="btn btn-primary ml-10">
Apply
</button>
</div>
</div>
)}
</div>
</div>
{this.state.isOpen && <div className="overlay-mask bg-grey open" />}
</>
);
}
}