diff --git a/package-lock.json b/package-lock.json index eb5f496..a4262f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "react-dropzone": "^11.4.2", "react-icons": "^4.3.1", "react-indiana-drag-scroll": "^2.1.0", + "react-multi-date-picker": "^3.3.0", "react-redux": "^7.2.6", "react-router-dom": "^6.2.1", "react-scripts": "5.0.0", @@ -14156,6 +14157,11 @@ "react-dom": ">=16.8.0" } }, + "node_modules/react-date-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.5.tgz", + "integrity": "sha512-iAtF2nz+jZjy3w36qKx2MsxXqbiL7UgGljz9jPjLWxWCl4rDTZUMS/i7hKKiVbL1lJlmubz1Xu6W3i6CW06kTg==" + }, "node_modules/react-dev-utils": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz", @@ -14320,6 +14326,15 @@ "react": ">= 16.8" } }, + "node_modules/react-element-popper": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.6.tgz", + "integrity": "sha512-8va7mUmrKIkUnaM2t5Dyctd8cjgVgVcrv5vVD0FRay0sN6EPBCKa0bDi1/KmVDAjfgSIn7zQnjtc4VojcGrkgQ==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-error-overlay": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", @@ -14391,6 +14406,19 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", "peer": true }, + "node_modules/react-multi-date-picker": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-3.3.0.tgz", + "integrity": "sha512-xAounECAkPUSnwmGCXlLevRLS9lt6IrGzD1VUJKZPbuzauwxaJLZ0mW6vnGUjui60qBY/uQeEmJftq+pEq5HgQ==", + "dependencies": { + "react-date-object": "^2.1.5", + "react-element-popper": "^2.1.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-redux": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz", @@ -27595,6 +27623,11 @@ "integrity": "sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==", "requires": {} }, + "react-date-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.5.tgz", + "integrity": "sha512-iAtF2nz+jZjy3w36qKx2MsxXqbiL7UgGljz9jPjLWxWCl4rDTZUMS/i7hKKiVbL1lJlmubz1Xu6W3i6CW06kTg==" + }, "react-dev-utils": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz", @@ -27713,6 +27746,12 @@ "prop-types": "^15.7.2" } }, + "react-element-popper": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.6.tgz", + "integrity": "sha512-8va7mUmrKIkUnaM2t5Dyctd8cjgVgVcrv5vVD0FRay0sN6EPBCKa0bDi1/KmVDAjfgSIn7zQnjtc4VojcGrkgQ==", + "requires": {} + }, "react-error-overlay": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", @@ -27766,6 +27805,15 @@ "integrity": "sha512-oaBdIlyu+g7PhLUvwnCsl/wvu+5tGB9I3RLjcrYLt6U1sUMzQJqplKtVxXRv9TZqRdNaAU5qNOP+dRs55QKjsA==", "requires": {} }, + "react-multi-date-picker": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-3.3.0.tgz", + "integrity": "sha512-xAounECAkPUSnwmGCXlLevRLS9lt6IrGzD1VUJKZPbuzauwxaJLZ0mW6vnGUjui60qBY/uQeEmJftq+pEq5HgQ==", + "requires": { + "react-date-object": "^2.1.5", + "react-element-popper": "^2.1.6" + } + }, "react-redux": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz", diff --git a/package.json b/package.json index a0d4f50..ab9dd2e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react-dropzone": "^11.4.2", "react-icons": "^4.3.1", "react-indiana-drag-scroll": "^2.1.0", + "react-multi-date-picker": "^3.3.0", "react-redux": "^7.2.6", "react-router-dom": "^6.2.1", "react-scripts": "5.0.0", diff --git a/src/components/DateTimePicker/components/Calendar.js b/src/components/DateTimePicker/components/Calendar.js new file mode 100644 index 0000000..9c291f2 --- /dev/null +++ b/src/components/DateTimePicker/components/Calendar.js @@ -0,0 +1,181 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import DaysViewHeading from './DaysViewHeading'; +import DaysOfWeek from './DaysOfWeek'; +import MonthSelector from './MonthSelector'; +import Day from './Day'; +import { getDaysOfMonth } from '../utils/moment-helper'; +import moment from 'moment-jalali'; +import onClickOutside from 'react-onclickoutside'; + +// Load Persian localisation +moment.loadPersian(); + +export class DateTimeCalendar extends Component { + static propTypes = { + min: PropTypes.object, + max: PropTypes.object, + styles: PropTypes.object, + selectedDay: PropTypes.object, + defaultMonth: PropTypes.object, + onSelect: PropTypes.func, + onClickOutside: PropTypes.func, + containerProps: PropTypes.object + }; + + static childContextTypes = { + nextMonth: PropTypes.func.isRequired, + prevMonth: PropTypes.func.isRequired, + setCalendarMode: PropTypes.func.isRequired, + setMonth: PropTypes.func.isRequired + }; + + static defaultProps = { + styles: require('../styles/basic.css'), + containerProps: {} + }; + + state = { + month: this.props.defaultMonth || this.props.selectedDay || moment(this.props.min), + selectedDay: this.props.selectedDay || null, + mode: 'days' + }; + + getChildContext() { + return { + nextMonth: this.nextMonth.bind(this), + prevMonth: this.prevMonth.bind(this), + setCalendarMode: this.setMode.bind(this), + setMonth: this.setMonth.bind(this) + }; + } + + componentWillReceiveProps({ selectedDay, defaultMonth, min }) { + if (this.props.selectedDay !== selectedDay) { + this.selectDay(selectedDay); + } else if (defaultMonth && this.props.defaultMonth !== defaultMonth && this.state.month === this.props.defaultMonth) { + this.setMonth(defaultMonth); + } else if (min && this.props.min !== min && this.state.month.isSame(this.props.min)) { + this.setMonth(min.clone()); + } + } + + setMode(mode) { + this.setState({ mode }); + } + + setMonth(month) { + this.setState({ month }); + } + + nextMonth() { + this.setState({ + month: this.state.month.clone().add(1, 'jMonth') + }); + } + + prevMonth() { + this.setState({ + month: this.state.month.clone().subtract(1, 'jMonth') + }); + } + + selectDay(selectedDay) { + const { month } = this.state; + + // Because there's no `m1.isSame(m2, 'jMonth')` + if (selectedDay.format('jYYYYjMM') !== month.format('jYYYYjMM')) { + this.setState({ month: selectedDay }); + } + + this.setState({ selectedDay }); + } + + handleClickOnDay = selectedDay => { + const { onSelect } = this.props; + this.selectDay(selectedDay); + if (onSelect) { + onSelect(selectedDay); + } + }; + + handleClickOutside(event) { + if (this.props.onClickOutside) { + this.props.onClickOutside(event); + } + } + + days = null; + lastRenderedMonth = null; + + renderMonthSelector() { + const { month } = this.state; + const { styles } = this.props; + return (); + } + + renderDays() { + const { month, selectedDay } = this.state; + const { children, min, max, styles, outsideClickIgnoreClass } = this.props; + + let days; + + if (this.lastRenderedMonth === month) { + days = this.days; + } else { + days = getDaysOfMonth(month); + this.days = days; + this.lastRenderedMonth = month; + } + + return ( +
+ {children} + + +
+ { + days.map(day => { + const isCurrentMonth = day.format('jMM') === month.format('jMM'); + const disabled = (min ? day.isBefore(min) : false) || (max ? day.isAfter(max) : false); + const selected = selectedDay ? selectedDay.isSame(day, 'day') : false; + + return ( + + ); + }) + } +
+
+ ); + } + + render() { + const { + selectedDay, + min, + max, + onClickOutside, + outsideClickIgnoreClass, + styles, + className + } = this.props; + const { mode } = this.state; + + return ( +
+ { mode === 'monthSelector' ? this.renderMonthSelector() : this.renderDays() } +
+ ); + } +} + +export default onClickOutside(Calendar); diff --git a/src/components/DateTimePicker/components/DatePicker.js b/src/components/DateTimePicker/components/DatePicker.js new file mode 100644 index 0000000..ec7177a --- /dev/null +++ b/src/components/DateTimePicker/components/DatePicker.js @@ -0,0 +1,204 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment-jalali'; +import TetherComponent from 'react-tether'; +import Calendar from './Calendar'; +import classnames from 'classnames'; + +export const outsideClickIgnoreClass = 'ignore--click--outside'; + +export default class DateTimePicker extends Component { + static propTypes = { + value: PropTypes.object, + defaultValue: PropTypes.object, + onChange: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + children: PropTypes.node, + min: PropTypes.object, + max: PropTypes.object, + defaultMonth: PropTypes.object, + inputFormat: PropTypes.string, + removable: PropTypes.bool, + timePickerComponent: PropTypes.func, + calendarStyles: PropTypes.object, + calendarContainerProps: PropTypes.object + }; + + static defaultProps = { + inputFormat: 'jYYYY/jM/jD', + calendarStyles: require('../styles/basic.css'), + calendarContainerProps: {} + }; + + state = { + isOpen: false, + momentValue: this.props.defaultValue || null, + inputValue: this.props.defaultValue ? + this.props.defaultValue.format(this.props.inputFormat) : '' + }; + + setOpen(isOpen) { + this.setState({ isOpen }); + } + + componentWillReceiveProps(nextProps) { + if ('value' in nextProps && nextProps.value !== this.props.value) { + this.setMomentValue(nextProps.value); + } + } + + setMomentValue(momentValue) { + const { inputFormat } = this.props; + + if (this.props.onChange) { + this.props.onChange(momentValue); + } + + let inputValue = ""; + if (momentValue) + inputValue = momentValue.format(inputFormat); + this.setState({ momentValue, inputValue }); + } + + handleFocus() { + this.setOpen(true); + } + + handleBlur(event) { + const { onBlur, inputFormat } = this.props; + const { isOpen, momentValue } = this.state; + + if (isOpen) { + this.refs.input.focus(); + } else if (onBlur) { + onBlur(event); + } + + if (momentValue) { + const inputValue = momentValue.format(inputFormat); + this.setState({ inputValue }); + } + } + + handleClickOutsideCalendar() { + this.setOpen(false); + } + + handleSelectDay(selectedDay) { + const { momentValue: oldValue } = this.state; + let momentValue = selectedDay.clone(); + + if (oldValue) { + momentValue = momentValue + .set({ + hour: oldValue.hours(), + minute: oldValue.minutes(), + second: oldValue.seconds() + }); + } + + this.setMomentValue(momentValue); + } + + handleInputChange(event) { + const { inputFormat } = this.props; + const inputValue = event.target.value; + const momentValue = moment(inputValue, inputFormat); + + if (momentValue.isValid()) { + this.setState({ momentValue }); + } + + this.setState({ inputValue }); + } + + handleInputClick() { + if (!this.props.disabled) { + this.setOpen(true) + } + } + + renderInput() { + let { isOpen, inputValue } = this.state; + + if (this.props.value) { + let value = this.props.value; + let inputFormat = this.props.inputFormat; + inputValue = value.format(inputFormat); + } + + const className = classnames(this.props.className, { + [outsideClickIgnoreClass]: isOpen + }); + + return ( +
+ +
+ ); + } + + renderCalendar() { + const { momentValue } = this.state; + const { timePickerComponent: TimePicker, onChange, min, max, defaultMonth, calendarStyles, calendarContainerProps } = this.props; + + return ( +
+ + { + TimePicker ? ( + + ) : null + } + +
+ ); + } + + removeDate() { + const {onChange} = this.props; + if (onChange) { + onChange(''); + } + this.setState({ + input: '', + inputValue: '' + }); + } + + render() { + const { isOpen } = this.state; + + return ( + + { this.renderInput() } + { isOpen ? this.renderCalendar() : null } + + ); + } +} diff --git a/src/components/DateTimePicker/components/Day.js b/src/components/DateTimePicker/components/Day.js new file mode 100644 index 0000000..1b458e0 --- /dev/null +++ b/src/components/DateTimePicker/components/Day.js @@ -0,0 +1,61 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { persianNumber } from '../utils/persian'; + +const styles = { + wrapper: {}, + button: { + outline: 'none', + cursor: 'pointer' + } +}; + +export default class Day extends Component { + static propTypes = { + day: PropTypes.object.isRequired, + isCurrentMonth: PropTypes.bool, + disabled: PropTypes.bool, + selected: PropTypes.bool, + onClick: PropTypes.func + }; + + shouldComponentUpdate(nextProps) { + return nextProps.selected !== this.props.selected || + nextProps.disabled !== this.props.disabled || + nextProps.isCurrentMonth !== this.props.isCurrentMonth; + } + + handleClick(event) { + event.preventDefault(); + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + const { onClick, day } = this.props; + + if (onClick) { + onClick(day); + } + } + + render() { + const { day, disabled, selected, isCurrentMonth, onClick, styles, ...rest } = this.props; + + const className = classnames(styles.dayWrapper, { + [styles.selected]: selected, + [styles.currentMonth]: isCurrentMonth + }); + + return ( +
+ +
+ ); + } +} diff --git a/src/components/DateTimePicker/components/DaysOfWeek.js b/src/components/DateTimePicker/components/DaysOfWeek.js new file mode 100644 index 0000000..520c2c8 --- /dev/null +++ b/src/components/DateTimePicker/components/DaysOfWeek.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +// Day of week names for use in date-picker heading +const dayOfWeekNames = ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج']; + +export default class DaysOfWeek extends Component { + static propTypes = { + styles: PropTypes.object + }; + + render() { + const { styles } = this.props; + + return ( +
+ { dayOfWeekNames.map((name, key) =>
{name}
) } +
+ ); + } +} diff --git a/src/components/DateTimePicker/components/DaysViewHeading.js b/src/components/DateTimePicker/components/DaysViewHeading.js new file mode 100755 index 0000000..0d18211 --- /dev/null +++ b/src/components/DateTimePicker/components/DaysViewHeading.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { persianNumber } from '../utils/persian'; +import { leftArrow, rightArrow } from '../utils/assets'; + +export default class Heading extends Component { + static propTypes = { + month: PropTypes.object.isRequired + }; + + static contextTypes = { + styles: PropTypes.object, + nextMonth: PropTypes.func.isRequired, + prevMonth: PropTypes.func.isRequired, + setCalendarMode: PropTypes.func.isRequired + }; + + handleMonthClick(event) { + const { setCalendarMode } = this.context; + event.preventDefault(); + setCalendarMode('monthSelector'); + } + + render() { + const { nextMonth, prevMonth } = this.context; + const { month, styles } = this.props; + + return ( +
+ +
+ ); + } +} diff --git a/src/components/DateTimePicker/components/MonthSelector.js b/src/components/DateTimePicker/components/MonthSelector.js new file mode 100644 index 0000000..0e6a679 --- /dev/null +++ b/src/components/DateTimePicker/components/MonthSelector.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment-jalali'; +import classnames from 'classnames'; +import MonthsViewHeading from './MonthsViewHeading'; +import { persianNumber } from '../utils/persian'; +import { leftArrow, rightArrow } from '../utils/assets'; + +// List of months +const months = [ + 'فروردین', + 'اردیبهشت', + 'خرداد', + 'تیر', + 'مرداد', + 'شهریور', + 'مهر', + 'آبان', + 'آذر', + 'دی', + 'بهمن', + 'اسفند' +]; + +export default class MonthSelector extends Component { + static propTypes = { + styles: PropTypes.object, + selectedMonth: PropTypes.object.isRequired + }; + + static contextTypes = { + setCalendarMode: PropTypes.func.isRequired, + setMonth: PropTypes.func.isRequired + }; + + state = { + year: this.props.selectedMonth + }; + + nextYear() { + this.setState({ + year: this.state.year.clone().add(1, 'year') + }); + } + + prevYear() { + this.setState({ + year: this.state.year.clone().subtract(1, 'year') + }); + } + + handleClick(key) { + const { setMonth, setCalendarMode } = this.context; + setMonth(moment(key, 'jM-jYYYY')); + setCalendarMode('days'); + } + + render() { + const { year } = this.state; + const { selectedMonth, styles } = this.props; + + return ( +
+ +
+ { + months.map((name, key) => { + const buttonFingerprint = (key + 1) + '-' + year.format('jYYYY'); + const selectedMonthFingerprint = selectedMonth.format('jM-jYYYY'); + const isCurrent = selectedMonthFingerprint === buttonFingerprint; + + const className = classnames(styles.monthWrapper, { + [styles.selected]: isCurrent + }); + + return ( +
+ +
+ ); + }) + } +
+
); + } +} diff --git a/src/components/DateTimePicker/components/MonthsViewHeading.js b/src/components/DateTimePicker/components/MonthsViewHeading.js new file mode 100644 index 0000000..74c7e8f --- /dev/null +++ b/src/components/DateTimePicker/components/MonthsViewHeading.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { persianNumber } from '../utils/persian'; +import { leftArrow, rightArrow } from '../utils/assets'; + +export default class MonthsViewHeading extends Component { + static propTypes = { + year: PropTypes.object.isRequired, + onNextYear: PropTypes.func.isRequired, + onPrevYear: PropTypes.func.isRequired + }; + + static contextTypes = { + styles: PropTypes.object + }; + + render() { + const { year, styles } = this.props; + + return ( +
+ + { persianNumber(year.format('jYYYY')) } + +
+ ); + } +} diff --git a/src/components/DateTimePicker/index.js b/src/components/DateTimePicker/index.js new file mode 100644 index 0000000..b7b4807 --- /dev/null +++ b/src/components/DateTimePicker/index.js @@ -0,0 +1,3 @@ +// export {DateTimeCalendar as default} from './components/Calendar'; +// export {DateTimePicker as default} from './components/DatePicker'; +// export { outsideClickIgnoreClass as default } from './components/DatePicker' \ No newline at end of file diff --git a/src/components/DateTimePicker/styles/basic.css b/src/components/DateTimePicker/styles/basic.css new file mode 100644 index 0000000..ebfb57a --- /dev/null +++ b/src/components/DateTimePicker/styles/basic.css @@ -0,0 +1,156 @@ +.calendarContainer { + border-radius: 3px; + box-shadow: 0 3px 10px #dbdbdb; + border: 1px solid #cccccc; + width: 300px; + margin: auto; + text-align: center; + padding: 10px; + background-color: #fff; +} + +.calendarContainer * { + box-sizing: border-box; +} + +.calendarContainer .dayPickerContainer:after, +.calendarContainer .monthsList:after, +.calendarContainer .daysOfWeek:after { + content: ''; + display: block; + clear: both; +} + +/* Heading */ +.calendarContainer .heading { + height: 42px; + font-weight: bold; + margin-bottom: 10px; +} + +.calendarContainer .heading > button { + border-radius: 3px; + background: none; + margin: 5px 0; + border: 1px solid #F7F7F7; + text-align: center; + line-height: 30px; + width: 36px; + height: 32px; + cursor: pointer; +} + +.calendarContainer .heading > button:hover { + background-color: #f2f2f2; +} + +.calendarContainer .heading > span { + line-height: 35px; +} + +.calendarContainer .heading svg { + width: 10px; +} + +.calendarContainer .heading .prev { + float: right; +} + +.calendarContainer .heading .next { + float: left; +} + +.calendarContainer .heading .title { + line-height: 32px; + width: 120px; + height: 32px; + font-size: 1em; + margin: 5px 0; + border: 1px solid #F7F7F7; + text-align: center; + display: inline-block; + font-weight: normal; +} + +/* Day wrapper styles */ +.calendarContainer .dayWrapper { + padding: 5; + float: right; + width: 14.28571429%; +} + +/* Day wrapper button styles */ +.calendarContainer .dayWrapper button { + border: none; + background: none; + outline: none; + width: 100%; + height: 36px; + border-radius: 3px; + cursor: pointer; +} + +.calendarContainer .dayWrapper button:hover { + background-color: #eeeeff; +} + +.calendarContainer .dayWrapper button[disabled] { + color: #aaa; + cursor: not-allowed; + background-color: #ebebeb; +} + +.calendarContainer .dayWrapper button.selected { + background-color: #337ab7; + color: #ffffff; +} + +.calendarContainer .dayWrapper:not(.currentMonth) button { + opacity: .5; +} + +/* Days of week row */ +.calendarContainer .daysOfWeek { + border-bottom: 1px solid #EEE; + margin-bottom: 5px; + padding-bottom: 5px; + display: flex; + width: 100%; +} + +.calendarContainer .daysOfWeek > div { + flex-grow: 1; + justify-content: space-between; +} + +/* Month selector portion */ +.calendarContainer .monthsList { + clear: both; + width: 100%; +} + +.calendarContainer .monthsList button { + width: 33.33333332%; + height: 25%; + float: right; + border: 1px solid #F9F9F9; + outline: none; + font-size: 1em; + background: #fff; + padding: 10px 0; + cursor: pointer; +} + +.calendarContainer .monthsList button:hover { + background: #eeeeee; + cursor: pointer; +} + +/* Selected state of buttons */ +.calendarContainer .selected button, +.calendarContainer .selected button:hover, +.calendarContainer .selected button:active, +.calendarContainer .selected button:focus { + background-color: #337ab7; + color: #ffffff; +} diff --git a/src/components/DateTimePicker/utils/assets.js b/src/components/DateTimePicker/utils/assets.js new file mode 100644 index 0000000..44d53fc --- /dev/null +++ b/src/components/DateTimePicker/utils/assets.js @@ -0,0 +1,11 @@ +export const leftArrow = { + __html: '' +}; + +export const rightArrow = { + __html: '' +}; + +export const remove = { + __html: '' +}; diff --git a/src/components/DateTimePicker/utils/moment-helper.js b/src/components/DateTimePicker/utils/moment-helper.js new file mode 100644 index 0000000..839c080 --- /dev/null +++ b/src/components/DateTimePicker/utils/moment-helper.js @@ -0,0 +1,25 @@ +/** + * Get days of a month that should be shown on a month page + * + * @param month A moment object + * @returns {Array} + */ +export function getDaysOfMonth(month) { + const days = []; + + const current = month.clone().startOf('jMonth'); + const end = month.clone().endOf('jMonth'); + + // Set start to the first day of week in the last month + current.subtract((current.day() + 1) % 7, 'days'); + + // Set end to the last day of week in the next month + end.add(6 - (end.day() + 1) % 7, 'days'); + + while (current.isBefore(end)) { + days.push(current.clone()); + current.add(1, 'days'); + } + + return days; +} diff --git a/src/components/DateTimePicker/utils/persian.js b/src/components/DateTimePicker/utils/persian.js new file mode 100644 index 0000000..3ec1ef0 --- /dev/null +++ b/src/components/DateTimePicker/utils/persian.js @@ -0,0 +1,29 @@ +const latinToPersianMap = ['۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹', '۰']; +const latinNumbers = [/1/g, /2/g, /3/g, /4/g, /5/g, /6/g, /7/g, /8/g, /9/g, /0/g]; + +function prepareNumber(input) { + let string; + if (typeof input === 'number') { + string = input.toString(); + } else if (typeof input === 'undefined') { + string = ''; + } else { + string = input; + } + + return string; +} + +function latinToPersian(string) { + let result = string; + + for (let index = 0; index < 10; index++) { + result = result.replace(latinNumbers[index], latinToPersianMap[index]); + } + + return result; +} + +export function persianNumber(input) { + return latinToPersian(prepareNumber(input)); +} diff --git a/src/view/addExam/components/question.js b/src/view/addExam/components/question.js index b9a004c..f005f86 100644 --- a/src/view/addExam/components/question.js +++ b/src/view/addExam/components/question.js @@ -14,9 +14,9 @@ const Question = ({data}) => { 'ز', ] return( -
-
-
+
+
+
{data.title}
@@ -28,11 +28,11 @@ const Question = ({data}) => {
-
+
{ data.options.map( (item) => ( -
+

{gozine[item.id - 1]}

{item.title}

diff --git a/src/view/addExam/views/AddDescription.js b/src/view/addExam/views/AddDescription.js index 73dca36..ba3a04c 100644 --- a/src/view/addExam/views/AddDescription.js +++ b/src/view/addExam/views/AddDescription.js @@ -2,82 +2,116 @@ import React, { useState } from "react"; import { connect } from "react-redux"; import Button from "../../../components/Button"; -import DatePicker from "../../../components/Calendar/CalendarComponents/DatePicker"; +import DatePicker from "react-multi-date-picker"; +import TimePicker from "react-multi-date-picker/plugins/time_picker" +import persian from "react-date-object/calendars/persian" +import persian_fa from "react-date-object/locales/persian_fa" +import InputIcon from "react-multi-date-picker/components/input_icon" import RangeDatePicker from "../../../components/RangeDatePicker"; -const AddExamDescription = ({grades, addExamState, dispatch, stepClick, backStep}) => { +const AddExamDescription = ({ grades, addExamState, dispatch, stepClick, backStep }) => { const [name, setName] = useState(null); const [type, setType] = useState(null); const [description, setDescription] = useState(null); - const payload = {name: name, description: description, type: type}; - - return( + const payload = { name: name, description: description, type: type }; + const CostumeDatePickerInput = ({ openCalendar, value, handleValueChange, title }) => { + return( + + ) + } + return ( <>
-
- تنظیمات آزمون - -
-

لطفا مشخضات آزمون را تکمیل کنید

-
- -
-
- setName(e.target.value)} - /> +
+ تنظیمات آزمون - - - +
+

لطفا مشخضات آزمون را تکمیل کنید

- +
+
+ setName(e.target.value)} + /> + } + plugins={[ + , + + ]} + /> + } + plugins={[ + , + + ]} + /> +
+ + + + +
-
- تنظیمات آزمون - -
-

لطفا یک مدل از آزمون را انتخاب کنید

-
+
+ تنظیمات آزمون + +
+

لطفا یک مدل از آزمون را انتخاب کنید

+
+ +
+ +
setType('bank')}> + بانک سوالات -
- -
setType('bank')}> - بانک سوالات - -
-
setType('pdf')}> - ذفترچه سوالات - +
+
setType('pdf')}> + ذفترچه سوالات -
- -
+ +
+ +
- - +
diff --git a/yarn.lock b/yarn.lock index 4209540..8bd2c05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7655,6 +7655,11 @@ "resolved" "https://registry.npmjs.org/react-colorful/-/react-colorful-5.5.1.tgz" "version" "5.5.1" +"react-date-object@^2.1.5": + "integrity" "sha512-iAtF2nz+jZjy3w36qKx2MsxXqbiL7UgGljz9jPjLWxWCl4rDTZUMS/i7hKKiVbL1lJlmubz1Xu6W3i6CW06kTg==" + "resolved" "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.5.tgz" + "version" "2.1.5" + "react-dev-utils@^12.0.0": "integrity" "sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ==" "resolved" "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz" @@ -7714,6 +7719,11 @@ "file-selector" "^0.2.2" "prop-types" "^15.7.2" +"react-element-popper@^2.1.6": + "integrity" "sha512-8va7mUmrKIkUnaM2t5Dyctd8cjgVgVcrv5vVD0FRay0sN6EPBCKa0bDi1/KmVDAjfgSIn7zQnjtc4VojcGrkgQ==" + "resolved" "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.6.tgz" + "version" "2.1.6" + "react-error-overlay@^6.0.10": "integrity" "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==" "resolved" "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz" @@ -7775,6 +7785,14 @@ "deep-equal" "1.0.1" "supercluster" "^7.0.0" +"react-multi-date-picker@^3.3.0": + "integrity" "sha512-xAounECAkPUSnwmGCXlLevRLS9lt6IrGzD1VUJKZPbuzauwxaJLZ0mW6vnGUjui60qBY/uQeEmJftq+pEq5HgQ==" + "resolved" "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-3.3.0.tgz" + "version" "3.3.0" + dependencies: + "react-date-object" "^2.1.5" + "react-element-popper" "^2.1.6" + "react-redux@^7.2.6": "integrity" "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==" "resolved" "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz"