After Width: | Height: | Size: 489 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 725 B |
After Width: | Height: | Size: 570 B |
After Width: | Height: | Size: 622 B |
After Width: | Height: | Size: 378 KiB |
After Width: | Height: | Size: 543 B |
After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 545 B |
After Width: | Height: | Size: 545 B |
After Width: | Height: | Size: 545 B |
After Width: | Height: | Size: 857 B |
After Width: | Height: | Size: 58 KiB |
@ -0,0 +1,245 @@ |
|||||||
|
import React, { useState, useRef, useEffect } from "react"; |
||||||
|
|
||||||
|
import { |
||||||
|
getDateAccordingToMonth, |
||||||
|
shallowClone, |
||||||
|
getValueType, |
||||||
|
} from "./shared/generalUtils"; |
||||||
|
import { |
||||||
|
TYPE_SINGLE_DATE, |
||||||
|
TYPE_RANGE, |
||||||
|
TYPE_MUTLI_DATE, |
||||||
|
} from "./shared/constants"; |
||||||
|
import { useLocaleUtils, useLocaleLanguage } from "./shared/hooks"; |
||||||
|
|
||||||
|
import { Header, MonthSelector, YearSelector, DaysList } from "./components"; |
||||||
|
|
||||||
|
const Calendar = ({ |
||||||
|
value, |
||||||
|
onChange, |
||||||
|
onDisabledDayError, |
||||||
|
calendarClassName, |
||||||
|
calendarTodayClassName, |
||||||
|
calendarSelectedDayClassName, |
||||||
|
calendarRangeStartClassName, |
||||||
|
calendarRangeBetweenClassName, |
||||||
|
calendarRangeEndClassName, |
||||||
|
disabledDays, |
||||||
|
colorPrimary, |
||||||
|
colorPrimaryLight, |
||||||
|
slideAnimationDuration, |
||||||
|
minimumDate, |
||||||
|
maximumDate, |
||||||
|
selectorStartingYear, |
||||||
|
selectorEndingYear, |
||||||
|
locale, |
||||||
|
shouldHighlightWeekends, |
||||||
|
renderFooter, |
||||||
|
customDaysClassName, |
||||||
|
events, |
||||||
|
renderEvents, |
||||||
|
type, |
||||||
|
}) => { |
||||||
|
const calendarElement = useRef(null); |
||||||
|
const [mainState, setMainState] = useState({ |
||||||
|
activeDate: null, |
||||||
|
monthChangeDirection: "", |
||||||
|
isMonthSelectorOpen: false, |
||||||
|
isYearSelectorOpen: false, |
||||||
|
}); |
||||||
|
const [eventInFooter, setEventInFooter] = useState(null); |
||||||
|
useEffect(() => { |
||||||
|
const handleKeyUp = ({ key }) => { |
||||||
|
/* istanbul ignore else */ |
||||||
|
if (key === "Tab") |
||||||
|
calendarElement.current.classList.remove("-noFocusOutline"); |
||||||
|
}; |
||||||
|
calendarElement.current.addEventListener("keyup", handleKeyUp, false); |
||||||
|
return () => { |
||||||
|
// calendarElement.current.removeEventListener('keyup', handleKeyUp, false);
|
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
const { getToday } = useLocaleUtils(locale); |
||||||
|
const { weekDays: weekDaysList, isRtl } = useLocaleLanguage(locale); |
||||||
|
const today = getToday(); |
||||||
|
|
||||||
|
const createStateToggler = (property) => () => { |
||||||
|
setMainState({ ...mainState, [property]: !mainState[property] }); |
||||||
|
}; |
||||||
|
|
||||||
|
const toggleMonthSelector = createStateToggler("isMonthSelectorOpen"); |
||||||
|
const toggleYearSelector = createStateToggler("isYearSelectorOpen"); |
||||||
|
|
||||||
|
const getComputedActiveDate = () => { |
||||||
|
const valueType = getValueType(value); |
||||||
|
if (valueType === TYPE_MUTLI_DATE && value.length) |
||||||
|
return shallowClone(value[0]); |
||||||
|
if (valueType === TYPE_SINGLE_DATE && value) return shallowClone(value); |
||||||
|
if (valueType === TYPE_RANGE && value.from) return shallowClone(value.from); |
||||||
|
return shallowClone(today); |
||||||
|
}; |
||||||
|
|
||||||
|
const activeDate = mainState.activeDate |
||||||
|
? shallowClone(mainState.activeDate) |
||||||
|
: getComputedActiveDate(); |
||||||
|
|
||||||
|
const weekdays = weekDaysList.map((weekDay) => ( |
||||||
|
<abbr |
||||||
|
key={weekDay.name} |
||||||
|
title={weekDay.name} |
||||||
|
className={`Calendar__weekDay ${type}`} |
||||||
|
> |
||||||
|
{type == "dashboard" ? weekDay.name : weekDay.short} |
||||||
|
</abbr> |
||||||
|
)); |
||||||
|
|
||||||
|
const handleMonthChange = (direction) => { |
||||||
|
setMainState({ |
||||||
|
...mainState, |
||||||
|
monthChangeDirection: direction, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const updateDate = () => { |
||||||
|
setMainState({ |
||||||
|
...mainState, |
||||||
|
activeDate: getDateAccordingToMonth( |
||||||
|
activeDate, |
||||||
|
mainState.monthChangeDirection |
||||||
|
), |
||||||
|
monthChangeDirection: "", |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const selectMonth = (newMonthNumber) => { |
||||||
|
setMainState({ |
||||||
|
...mainState, |
||||||
|
activeDate: { ...activeDate, month: newMonthNumber }, |
||||||
|
isMonthSelectorOpen: false, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const selectYear = (year) => { |
||||||
|
setMainState({ |
||||||
|
...mainState, |
||||||
|
activeDate: { ...activeDate, year }, |
||||||
|
isYearSelectorOpen: false, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={`Calendar ${type} -noFocusOutline ${calendarClassName} -${ |
||||||
|
isRtl ? "rtl" : "ltr" |
||||||
|
}`}
|
||||||
|
role="grid" |
||||||
|
style={{ |
||||||
|
"--cl-color-primary": colorPrimary, |
||||||
|
"--cl-color-primary-light": colorPrimaryLight, |
||||||
|
"--animation-duration": slideAnimationDuration, |
||||||
|
}} |
||||||
|
ref={calendarElement} |
||||||
|
> |
||||||
|
<Header |
||||||
|
maximumDate={maximumDate} |
||||||
|
minimumDate={minimumDate} |
||||||
|
activeDate={activeDate} |
||||||
|
onMonthChange={handleMonthChange} |
||||||
|
onMonthSelect={toggleMonthSelector} |
||||||
|
onYearSelect={toggleYearSelector} |
||||||
|
monthChangeDirection={mainState.monthChangeDirection} |
||||||
|
isMonthSelectorOpen={mainState.isMonthSelectorOpen} |
||||||
|
isYearSelectorOpen={mainState.isYearSelectorOpen} |
||||||
|
locale={locale} |
||||||
|
type={type} |
||||||
|
/> |
||||||
|
|
||||||
|
<MonthSelector |
||||||
|
isOpen={mainState.isMonthSelectorOpen} |
||||||
|
activeDate={activeDate} |
||||||
|
onMonthSelect={selectMonth} |
||||||
|
maximumDate={maximumDate} |
||||||
|
minimumDate={minimumDate} |
||||||
|
locale={locale} |
||||||
|
/> |
||||||
|
|
||||||
|
<YearSelector |
||||||
|
isOpen={mainState.isYearSelectorOpen} |
||||||
|
activeDate={activeDate} |
||||||
|
onYearSelect={selectYear} |
||||||
|
selectorStartingYear={selectorStartingYear} |
||||||
|
selectorEndingYear={selectorEndingYear} |
||||||
|
maximumDate={maximumDate} |
||||||
|
minimumDate={minimumDate} |
||||||
|
locale={locale} |
||||||
|
/> |
||||||
|
|
||||||
|
<div className={`Calendar__weekDays ${type}`}>{weekdays}</div> |
||||||
|
|
||||||
|
<DaysList |
||||||
|
activeDate={activeDate} |
||||||
|
value={value} |
||||||
|
monthChangeDirection={mainState.monthChangeDirection} |
||||||
|
onSlideChange={updateDate} |
||||||
|
disabledDays={disabledDays} |
||||||
|
onDisabledDayError={onDisabledDayError} |
||||||
|
minimumDate={minimumDate} |
||||||
|
maximumDate={maximumDate} |
||||||
|
onChange={onChange} |
||||||
|
calendarTodayClassName={calendarTodayClassName} |
||||||
|
calendarSelectedDayClassName={calendarSelectedDayClassName} |
||||||
|
calendarRangeStartClassName={calendarRangeStartClassName} |
||||||
|
calendarRangeEndClassName={calendarRangeEndClassName} |
||||||
|
calendarRangeBetweenClassName={calendarRangeBetweenClassName} |
||||||
|
locale={locale} |
||||||
|
shouldHighlightWeekends={shouldHighlightWeekends} |
||||||
|
customDaysClassName={customDaysClassName} |
||||||
|
events={events} |
||||||
|
renderFooter={renderFooter} |
||||||
|
renderEvents={renderEvents} |
||||||
|
isQuickSelectorOpen={ |
||||||
|
mainState.isYearSelectorOpen || mainState.isMonthSelectorOpen |
||||||
|
} |
||||||
|
showEvent={(event) => setEventInFooter(event)} |
||||||
|
type={type} |
||||||
|
/> |
||||||
|
<div className="Calendar__footer"> |
||||||
|
<div className={`footer__Events ${type}`}> |
||||||
|
{type == "dashboard" ? ( |
||||||
|
<> |
||||||
|
<div className="text-xl font-black text-blue-400"> |
||||||
|
رویداد ها:{" "} |
||||||
|
</div> |
||||||
|
<p className="text-blue-300 font-sansbold"> |
||||||
|
{eventInFooter |
||||||
|
? eventInFooter |
||||||
|
: "رویدادی در این روز وجود ندارد!"} |
||||||
|
</p> |
||||||
|
</> |
||||||
|
) : ( |
||||||
|
eventInFooter |
||||||
|
)} |
||||||
|
</div> |
||||||
|
{renderFooter()} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
Calendar.defaultProps = { |
||||||
|
minimumDate: null, |
||||||
|
maximumDate: null, |
||||||
|
colorPrimary: "#0eca2d", |
||||||
|
colorPrimaryLight: "#cff4d5", |
||||||
|
slideAnimationDuration: "0.4s", |
||||||
|
calendarClassName: "", |
||||||
|
locale: "en", |
||||||
|
value: null, |
||||||
|
renderFooter: () => null, |
||||||
|
renderEvents: () => null, |
||||||
|
customDaysClassName: [], |
||||||
|
events: [], |
||||||
|
}; |
||||||
|
|
||||||
|
export default Calendar; |
@ -0,0 +1,203 @@ |
|||||||
|
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react'; |
||||||
|
|
||||||
|
import Calendar from './Calendar'; |
||||||
|
import DatePickerInput from './DatePickerInput'; |
||||||
|
import { getValueType } from './shared/generalUtils'; |
||||||
|
import { TYPE_SINGLE_DATE, TYPE_MUTLI_DATE, TYPE_RANGE } from './shared/constants'; |
||||||
|
|
||||||
|
const DatePicker = ({ |
||||||
|
value, |
||||||
|
onChange, |
||||||
|
formatInputText, |
||||||
|
inputPlaceholder, |
||||||
|
inputClassName, |
||||||
|
inputName, |
||||||
|
renderInput, |
||||||
|
wrapperClassName, |
||||||
|
calendarClassName, |
||||||
|
calendarTodayClassName, |
||||||
|
calendarSelectedDayClassName, |
||||||
|
calendarRangeStartClassName, |
||||||
|
calendarRangeBetweenClassName, |
||||||
|
calendarRangeEndClassName, |
||||||
|
calendarPopperPosition, |
||||||
|
disabledDays, |
||||||
|
onDisabledDayError, |
||||||
|
colorPrimary, |
||||||
|
colorPrimaryLight, |
||||||
|
slideAnimationDuration, |
||||||
|
minimumDate, |
||||||
|
maximumDate, |
||||||
|
selectorStartingYear, |
||||||
|
selectorEndingYear, |
||||||
|
locale, |
||||||
|
shouldHighlightWeekends, |
||||||
|
renderFooter, |
||||||
|
customDaysClassName, |
||||||
|
}) => { |
||||||
|
const calendarContainerElement = useRef(null); |
||||||
|
const inputElement = useRef(null); |
||||||
|
const shouldPreventToggle = useRef(false); |
||||||
|
const [isCalendarOpen, setCalendarVisiblity] = useState(false); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const handleBlur = () => { |
||||||
|
setCalendarVisiblity(false); |
||||||
|
}; |
||||||
|
window.addEventListener('blur', handleBlur, false); |
||||||
|
return () => { |
||||||
|
window.removeEventListener('blur', handleBlur, false); |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
|
||||||
|
// handle input focus/blur
|
||||||
|
useEffect(() => { |
||||||
|
const valueType = getValueType(value); |
||||||
|
if (valueType === TYPE_MUTLI_DATE) return; // no need to close the calendar
|
||||||
|
const shouldCloseCalendar = |
||||||
|
valueType === TYPE_SINGLE_DATE ? !isCalendarOpen : !isCalendarOpen && value.from && value.to; |
||||||
|
if (shouldCloseCalendar) inputElement.current.blur(); |
||||||
|
}, [value, isCalendarOpen]); |
||||||
|
|
||||||
|
const handleBlur = e => { |
||||||
|
e.persist(); |
||||||
|
if (!isCalendarOpen) return; |
||||||
|
const isInnerElementFocused = calendarContainerElement.current.contains(e.relatedTarget); |
||||||
|
if (shouldPreventToggle.current) { |
||||||
|
shouldPreventToggle.current = false; |
||||||
|
inputElement.current.focus(); |
||||||
|
} else if (isInnerElementFocused && e.relatedTarget) { |
||||||
|
e.relatedTarget.focus(); |
||||||
|
} else { |
||||||
|
setCalendarVisiblity(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const openCalendar = () => { |
||||||
|
if (!shouldPreventToggle.current) setCalendarVisiblity(true); |
||||||
|
}; |
||||||
|
|
||||||
|
// Keep the calendar in the screen bounds if input is near the window edges
|
||||||
|
useLayoutEffect(() => { |
||||||
|
if (!isCalendarOpen) return; |
||||||
|
const { left, width, height, top } = calendarContainerElement.current.getBoundingClientRect(); |
||||||
|
const { clientWidth, clientHeight } = document.documentElement; |
||||||
|
const isOverflowingFromRight = left + width > clientWidth; |
||||||
|
const isOverflowingFromLeft = left < 0; |
||||||
|
const isOverflowingFromBottom = top + height > clientHeight; |
||||||
|
|
||||||
|
const getLeftStyle = () => { |
||||||
|
const overflowFromRightDistance = left + width - clientWidth; |
||||||
|
|
||||||
|
if (!isOverflowingFromRight && !isOverflowingFromLeft) return; |
||||||
|
const overflowFromLeftDistance = Math.abs(left); |
||||||
|
const rightPosition = isOverflowingFromLeft ? overflowFromLeftDistance : 0; |
||||||
|
|
||||||
|
const leftStyle = isOverflowingFromRight |
||||||
|
? `calc(50% - ${overflowFromRightDistance}px)` |
||||||
|
: `calc(50% + ${rightPosition}px)`; |
||||||
|
return leftStyle; |
||||||
|
}; |
||||||
|
|
||||||
|
calendarContainerElement.current.style.left = getLeftStyle(); |
||||||
|
if ( |
||||||
|
(calendarPopperPosition === 'auto' && isOverflowingFromBottom) || |
||||||
|
calendarPopperPosition === 'top' |
||||||
|
) { |
||||||
|
calendarContainerElement.current.classList.add('-top'); |
||||||
|
} |
||||||
|
}, [isCalendarOpen]); |
||||||
|
|
||||||
|
const handleCalendarChange = newValue => { |
||||||
|
const valueType = getValueType(value); |
||||||
|
onChange(newValue); |
||||||
|
if (valueType === TYPE_SINGLE_DATE) setCalendarVisiblity(false); |
||||||
|
else if (valueType === TYPE_RANGE && newValue.from && newValue.to) setCalendarVisiblity(false); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleKeyUp = ({ key }) => { |
||||||
|
switch (key) { |
||||||
|
case 'Enter': |
||||||
|
setCalendarVisiblity(true); |
||||||
|
break; |
||||||
|
case 'Escape': |
||||||
|
setCalendarVisiblity(false); |
||||||
|
shouldPreventToggle.current = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!isCalendarOpen && shouldPreventToggle.current) { |
||||||
|
inputElement.current.focus(); |
||||||
|
shouldPreventToggle.current = false; |
||||||
|
} |
||||||
|
}, [shouldPreventToggle, isCalendarOpen]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
onFocus={openCalendar} |
||||||
|
onBlur={handleBlur} |
||||||
|
onKeyUp={handleKeyUp} |
||||||
|
className={`DatePicker ${wrapperClassName}`} |
||||||
|
role="presentation" |
||||||
|
> |
||||||
|
<DatePickerInput |
||||||
|
ref={inputElement} |
||||||
|
formatInputText={formatInputText} |
||||||
|
value={value} |
||||||
|
inputPlaceholder={inputPlaceholder} |
||||||
|
inputClassName={inputClassName} |
||||||
|
renderInput={renderInput} |
||||||
|
inputName={inputName} |
||||||
|
locale={locale} |
||||||
|
/> |
||||||
|
{isCalendarOpen && ( |
||||||
|
<> |
||||||
|
<div |
||||||
|
ref={calendarContainerElement} |
||||||
|
className="DatePicker__calendarContainer" |
||||||
|
data-testid="calendar-container" |
||||||
|
role="presentation" |
||||||
|
onMouseDown={() => { |
||||||
|
shouldPreventToggle.current = true; |
||||||
|
}} |
||||||
|
> |
||||||
|
<Calendar |
||||||
|
value={value} |
||||||
|
onChange={handleCalendarChange} |
||||||
|
calendarClassName={calendarClassName} |
||||||
|
calendarTodayClassName={calendarTodayClassName} |
||||||
|
calendarSelectedDayClassName={calendarSelectedDayClassName} |
||||||
|
calendarRangeStartClassName={calendarRangeStartClassName} |
||||||
|
calendarRangeBetweenClassName={calendarRangeBetweenClassName} |
||||||
|
calendarRangeEndClassName={calendarRangeEndClassName} |
||||||
|
disabledDays={disabledDays} |
||||||
|
colorPrimary={colorPrimary} |
||||||
|
colorPrimaryLight={colorPrimaryLight} |
||||||
|
slideAnimationDuration={slideAnimationDuration} |
||||||
|
onDisabledDayError={onDisabledDayError} |
||||||
|
minimumDate={minimumDate} |
||||||
|
maximumDate={maximumDate} |
||||||
|
selectorStartingYear={selectorStartingYear} |
||||||
|
selectorEndingYear={selectorEndingYear} |
||||||
|
locale={locale} |
||||||
|
shouldHighlightWeekends={shouldHighlightWeekends} |
||||||
|
renderFooter={renderFooter} |
||||||
|
customDaysClassName={customDaysClassName} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="DatePicker__calendarArrow" /> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
DatePicker.defaultProps = { |
||||||
|
wrapperClassName: '', |
||||||
|
locale: 'en', |
||||||
|
calendarPopperPosition: 'auto', |
||||||
|
}; |
||||||
|
|
||||||
|
export default DatePicker; |
@ -0,0 +1,93 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { useLocaleUtils, useLocaleLanguage } from './shared/hooks'; |
||||||
|
import { putZero, getValueType } from './shared/generalUtils'; |
||||||
|
import { TYPE_SINGLE_DATE, TYPE_RANGE, TYPE_MUTLI_DATE } from './shared/constants'; |
||||||
|
|
||||||
|
const DatePickerInput = React.forwardRef( |
||||||
|
( |
||||||
|
{ value, inputPlaceholder, inputClassName, inputName, formatInputText, renderInput, locale }, |
||||||
|
ref, |
||||||
|
) => { |
||||||
|
const { getLanguageDigits } = useLocaleUtils(locale); |
||||||
|
const { |
||||||
|
from: fromWord, |
||||||
|
to: toWord, |
||||||
|
yearLetterSkip, |
||||||
|
digitSeparator, |
||||||
|
defaultPlaceholder, |
||||||
|
isRtl, |
||||||
|
} = useLocaleLanguage(locale); |
||||||
|
|
||||||
|
const getSingleDayValue = () => { |
||||||
|
if (!value) return ''; |
||||||
|
const year = getLanguageDigits(value.year); |
||||||
|
const month = getLanguageDigits(putZero(value.month)); |
||||||
|
const day = getLanguageDigits(putZero(value.day)); |
||||||
|
return `${year}/${month}/${day}`; |
||||||
|
}; |
||||||
|
|
||||||
|
const getDayRangeValue = () => { |
||||||
|
if (!value.from || !value.to) return ''; |
||||||
|
const { from, to } = value; |
||||||
|
const fromText = `${getLanguageDigits(putZero(from.year)) |
||||||
|
.toString() |
||||||
|
.slice(yearLetterSkip)}/${getLanguageDigits(putZero(from.month))}/${getLanguageDigits( |
||||||
|
putZero(from.day), |
||||||
|
)}`;
|
||||||
|
const toText = `${getLanguageDigits(putZero(to.year)) |
||||||
|
.toString() |
||||||
|
.slice(yearLetterSkip)}/${getLanguageDigits(putZero(to.month))}/${getLanguageDigits( |
||||||
|
putZero(to.day), |
||||||
|
)}`;
|
||||||
|
return `${fromWord} ${fromText} ${toWord} ${toText}`; |
||||||
|
}; |
||||||
|
|
||||||
|
const getMultiDateValue = () => { |
||||||
|
return value.map(date => getLanguageDigits(date.day)).join(`${digitSeparator} `); |
||||||
|
}; |
||||||
|
|
||||||
|
const getValue = () => { |
||||||
|
if (formatInputText()) return formatInputText(); |
||||||
|
const valueType = getValueType(value); |
||||||
|
switch (valueType) { |
||||||
|
case TYPE_SINGLE_DATE: |
||||||
|
return getSingleDayValue(); |
||||||
|
case TYPE_RANGE: |
||||||
|
return getDayRangeValue(); |
||||||
|
case TYPE_MUTLI_DATE: |
||||||
|
return getMultiDateValue(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const placeholderValue = inputPlaceholder || defaultPlaceholder; |
||||||
|
const render = () => { |
||||||
|
return ( |
||||||
|
renderInput({ ref }) || ( |
||||||
|
<input |
||||||
|
data-testid="datepicker-input" |
||||||
|
readOnly |
||||||
|
ref={ref} |
||||||
|
value={getValue()} |
||||||
|
name={inputName} |
||||||
|
placeholder={placeholderValue} |
||||||
|
className={`DatePicker__input -${isRtl ? 'rtl' : 'ltr'} ${inputClassName}`} |
||||||
|
aria-label={placeholderValue} |
||||||
|
/> |
||||||
|
) |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
return render(); |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
DatePickerInput.defaultProps = { |
||||||
|
formatInputText: () => '', |
||||||
|
renderInput: () => null, |
||||||
|
inputPlaceholder: '', |
||||||
|
inputClassName: '', |
||||||
|
inputName: '', |
||||||
|
}; |
||||||
|
|
||||||
|
export default DatePickerInput; |
@ -0,0 +1,399 @@ |
|||||||
|
import React, { useRef, useEffect } from 'react'; |
||||||
|
|
||||||
|
import { |
||||||
|
getSlideDate, |
||||||
|
handleSlideAnimationEnd, |
||||||
|
animateContent, |
||||||
|
} from '../shared/sliderHelpers'; |
||||||
|
import { |
||||||
|
deepCloneObject, |
||||||
|
isSameDay, |
||||||
|
createUniqueRange, |
||||||
|
getValueType, |
||||||
|
} from '../shared/generalUtils'; |
||||||
|
import { |
||||||
|
TYPE_SINGLE_DATE, |
||||||
|
TYPE_RANGE, |
||||||
|
TYPE_MUTLI_DATE, |
||||||
|
} from '../shared/constants'; |
||||||
|
import handleKeyboardNavigation from '../shared/keyboardNavigation'; |
||||||
|
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks'; |
||||||
|
|
||||||
|
const DaysList = ({ |
||||||
|
activeDate, |
||||||
|
value, |
||||||
|
monthChangeDirection, |
||||||
|
onSlideChange, |
||||||
|
disabledDays, |
||||||
|
onDisabledDayError, |
||||||
|
minimumDate, |
||||||
|
maximumDate, |
||||||
|
onChange, |
||||||
|
locale, |
||||||
|
calendarTodayClassName, |
||||||
|
calendarSelectedDayClassName, |
||||||
|
calendarRangeStartClassName, |
||||||
|
calendarRangeEndClassName, |
||||||
|
calendarRangeBetweenClassName, |
||||||
|
shouldHighlightWeekends, |
||||||
|
isQuickSelectorOpen, |
||||||
|
customDaysClassName, |
||||||
|
events, |
||||||
|
showEvent, |
||||||
|
type |
||||||
|
}) => { |
||||||
|
const calendarSectionWrapper = useRef(null); |
||||||
|
const { isRtl, weekDays: weekDaysList } = useLocaleLanguage(locale); |
||||||
|
const { |
||||||
|
getToday, |
||||||
|
isBeforeDate, |
||||||
|
checkDayInDayRange, |
||||||
|
getMonthFirstWeekday, |
||||||
|
getMonthLength, |
||||||
|
getLanguageDigits, |
||||||
|
getMonthName, |
||||||
|
} = useLocaleUtils(locale); |
||||||
|
const today = getToday(); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!monthChangeDirection) return; |
||||||
|
animateContent({ |
||||||
|
direction: monthChangeDirection, |
||||||
|
parent: calendarSectionWrapper.current, |
||||||
|
}); |
||||||
|
}, [monthChangeDirection]); |
||||||
|
|
||||||
|
const getDayRangeValue = (day) => { |
||||||
|
const clonedDayRange = deepCloneObject(value); |
||||||
|
const dayRangeValue = |
||||||
|
clonedDayRange.from && clonedDayRange.to |
||||||
|
? { from: null, to: null } |
||||||
|
: clonedDayRange; |
||||||
|
const dayRangeProp = !dayRangeValue.from ? 'from' : 'to'; |
||||||
|
dayRangeValue[dayRangeProp] = day; |
||||||
|
const { from, to } = dayRangeValue; |
||||||
|
|
||||||
|
// swap from and to values if from is later than to
|
||||||
|
if (isBeforeDate(dayRangeValue.to, dayRangeValue.from)) { |
||||||
|
dayRangeValue.from = to; |
||||||
|
dayRangeValue.to = from; |
||||||
|
} |
||||||
|
|
||||||
|
const checkIncludingDisabledDay = (disabledDay) => { |
||||||
|
return checkDayInDayRange({ |
||||||
|
day: disabledDay, |
||||||
|
from: dayRangeValue.from, |
||||||
|
to: dayRangeValue.to, |
||||||
|
}); |
||||||
|
}; |
||||||
|
const includingDisabledDay = disabledDays.find(checkIncludingDisabledDay); |
||||||
|
if (includingDisabledDay) { |
||||||
|
onDisabledDayError(includingDisabledDay); |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
return dayRangeValue; |
||||||
|
}; |
||||||
|
|
||||||
|
const getMultiDateValue = (day) => { |
||||||
|
const isAlreadyExisting = value.some((valueDay) => |
||||||
|
isSameDay(valueDay, day) |
||||||
|
); |
||||||
|
const addedToValue = [...value, day]; |
||||||
|
const removedFromValue = value.filter( |
||||||
|
(valueDay) => !isSameDay(valueDay, day) |
||||||
|
); |
||||||
|
return isAlreadyExisting ? removedFromValue : addedToValue; |
||||||
|
}; |
||||||
|
|
||||||
|
const handleDayClick = (day) => { |
||||||
|
const getNewValue = () => { |
||||||
|
const valueType = getValueType(value); |
||||||
|
switch (valueType) { |
||||||
|
case TYPE_SINGLE_DATE: |
||||||
|
return day; |
||||||
|
case TYPE_RANGE: |
||||||
|
return getDayRangeValue(day); |
||||||
|
case TYPE_MUTLI_DATE: |
||||||
|
return getMultiDateValue(day); |
||||||
|
} |
||||||
|
}; |
||||||
|
const newValue = getNewValue(); |
||||||
|
onChange(newValue); |
||||||
|
}; |
||||||
|
|
||||||
|
const isSingleDateSelected = (day) => { |
||||||
|
const valueType = getValueType(value); |
||||||
|
if (valueType === TYPE_SINGLE_DATE) return isSameDay(day, value); |
||||||
|
if (valueType === TYPE_MUTLI_DATE) |
||||||
|
return value.some((valueDay) => isSameDay(valueDay, day)); |
||||||
|
}; |
||||||
|
|
||||||
|
const getDayStatus = (dayItem) => { |
||||||
|
const isToday = isSameDay(dayItem, today); |
||||||
|
const isSelected = isSingleDateSelected(dayItem); |
||||||
|
const { from: startingDay, to: endingDay } = value || {}; |
||||||
|
const isStartingDayRange = isSameDay(dayItem, startingDay); |
||||||
|
const isEndingDayRange = isSameDay(dayItem, endingDay); |
||||||
|
const isWithinRange = checkDayInDayRange({ |
||||||
|
day: dayItem, |
||||||
|
from: startingDay, |
||||||
|
to: endingDay, |
||||||
|
}); |
||||||
|
return { |
||||||
|
isToday, |
||||||
|
isSelected, |
||||||
|
isStartingDayRange, |
||||||
|
isEndingDayRange, |
||||||
|
isWithinRange, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
const getDayClassNames = (dayItem) => { |
||||||
|
const { |
||||||
|
isToday, |
||||||
|
isSelected, |
||||||
|
isStartingDayRange, |
||||||
|
isEndingDayRange, |
||||||
|
isWithinRange, |
||||||
|
} = getDayStatus(dayItem); |
||||||
|
const customDayItemClassName = customDaysClassName.find((day) => |
||||||
|
isSameDay(dayItem, day) |
||||||
|
); |
||||||
|
|
||||||
|
const classNames = '' |
||||||
|
.concat(isToday && !isSelected ? ` -today ${calendarTodayClassName}` : '') |
||||||
|
.concat(!dayItem.isStandard ? ' -blank' : '') |
||||||
|
.concat(dayItem.isWeekend && shouldHighlightWeekends ? ' -weekend' : '') |
||||||
|
.concat( |
||||||
|
customDayItemClassName ? ` ${customDayItemClassName.className}` : '' |
||||||
|
) |
||||||
|
.concat(isSelected ? ` -selected ${calendarSelectedDayClassName}` : '') |
||||||
|
.concat( |
||||||
|
isStartingDayRange |
||||||
|
? ` -selectedStart ${calendarRangeStartClassName}` |
||||||
|
: '' |
||||||
|
) |
||||||
|
.concat( |
||||||
|
isEndingDayRange ? ` -selectedEnd ${calendarRangeEndClassName}` : '' |
||||||
|
) |
||||||
|
.concat( |
||||||
|
isWithinRange |
||||||
|
? ` -selectedBetween ${calendarRangeBetweenClassName}` |
||||||
|
: '' |
||||||
|
) |
||||||
|
.concat(dayItem.isDisabled ? ' -disabled' : ''); |
||||||
|
return classNames; |
||||||
|
}; |
||||||
|
|
||||||
|
const getViewMonthDays = (date) => { |
||||||
|
// to match month starting date with the correct weekday label
|
||||||
|
const prependingBlankDays = createUniqueRange( |
||||||
|
getMonthFirstWeekday(date), |
||||||
|
'starting-blank' |
||||||
|
); |
||||||
|
const standardDays = createUniqueRange(getMonthLength(date)).map((day) => ({ |
||||||
|
...day, |
||||||
|
isStandard: true, |
||||||
|
month: date.month, |
||||||
|
year: date.year, |
||||||
|
})); |
||||||
|
const allDays = [...prependingBlankDays, ...standardDays]; |
||||||
|
return allDays; |
||||||
|
}; |
||||||
|
|
||||||
|
const handleDayPress = ({ isDisabled, ...dayItem }) => { |
||||||
|
if (isDisabled) { |
||||||
|
onDisabledDayError(dayItem); // good for showing error messages
|
||||||
|
} else handleDayClick(dayItem); |
||||||
|
}; |
||||||
|
|
||||||
|
const isDayReachableByKeyboard = ({ |
||||||
|
isOnActiveSlide, |
||||||
|
isStandard, |
||||||
|
isSelected, |
||||||
|
isStartingDayRange, |
||||||
|
isToday, |
||||||
|
day, |
||||||
|
}) => { |
||||||
|
if (isQuickSelectorOpen || !isOnActiveSlide || !isStandard) return false; |
||||||
|
if (isSelected || isStartingDayRange || isToday || day === 1) return true; |
||||||
|
}; |
||||||
|
|
||||||
|
const renderEachWeekDays = ( |
||||||
|
{ id, value: day, month, year, isStandard }, |
||||||
|
index |
||||||
|
) => { |
||||||
|
const dayItem = { day, month, year }; |
||||||
|
const isInDisabledDaysRange = disabledDays.some((disabledDay) => |
||||||
|
isSameDay(dayItem, disabledDay) |
||||||
|
); |
||||||
|
const isBeforeMinimumDate = isBeforeDate(dayItem, minimumDate); |
||||||
|
const isAfterMaximumDate = isBeforeDate(maximumDate, dayItem); |
||||||
|
const isNotInValidRange = |
||||||
|
isStandard && (isBeforeMinimumDate || isAfterMaximumDate); |
||||||
|
const isDisabled = isInDisabledDaysRange || isNotInValidRange; |
||||||
|
const isWeekend = weekDaysList.some( |
||||||
|
(weekDayItem, weekDayItemIndex) => |
||||||
|
weekDayItem.isWeekend && weekDayItemIndex === index |
||||||
|
); |
||||||
|
const additionalClass = getDayClassNames({ |
||||||
|
...dayItem, |
||||||
|
isWeekend, |
||||||
|
isStandard, |
||||||
|
isDisabled, |
||||||
|
}); |
||||||
|
const dayLabel = `${weekDaysList[index].name}, ${day} ${getMonthName( |
||||||
|
month |
||||||
|
)} ${year}`;
|
||||||
|
const isOnActiveSlide = month === activeDate.month; |
||||||
|
const dayStatus = getDayStatus(dayItem); |
||||||
|
const { isSelected, isStartingDayRange, isEndingDayRange, isWithinRange } = |
||||||
|
dayStatus; |
||||||
|
const shouldEnableKeyboardNavigation = isDayReachableByKeyboard({ |
||||||
|
...dayItem, |
||||||
|
...dayStatus, |
||||||
|
isOnActiveSlide, |
||||||
|
isStandard, |
||||||
|
}); |
||||||
|
|
||||||
|
// showing Dots Of Events
|
||||||
|
const specialDate = (sDay, sMonth, sYear) => { |
||||||
|
return events.map((e) => { |
||||||
|
if (sDay == e.day && sMonth == e.month && sYear == e.year) { |
||||||
|
return <span key={'dot' + e.id} style={{ |
||||||
|
color: e.color |
||||||
|
}}>.</span>; |
||||||
|
|
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
const showEventDescription = (sDay, sMonth, sYear) => { |
||||||
|
|
||||||
|
return events.map((e) => { |
||||||
|
|
||||||
|
if (sDay == e.day && sMonth == e.month && sYear == e.year) { |
||||||
|
|
||||||
|
return (<div key={'description' + e.id}><p>{e.description}</p></div>) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
return ( |
||||||
|
<div |
||||||
|
style={{ |
||||||
|
display: 'flex', |
||||||
|
flexDirection: 'column', |
||||||
|
alignItems: 'center', |
||||||
|
}} |
||||||
|
tabIndex={shouldEnableKeyboardNavigation ? '0' : '-1'} |
||||||
|
key={id} |
||||||
|
className={`Calendar__day ${type} -${isRtl ? 'rtl' : 'ltr'} ${additionalClass}`} |
||||||
|
onClick={() => { |
||||||
|
handleDayPress({ ...dayItem, isDisabled }); |
||||||
|
// console.log(specialDate(day));
|
||||||
|
// console.log(showEventDescription(day))
|
||||||
|
showEvent(showEventDescription(day, month, year)) |
||||||
|
|
||||||
|
}} |
||||||
|
onKeyDown={({ key }) => { |
||||||
|
/* istanbul ignore else */ |
||||||
|
if (key === 'Enter') handleDayPress({ ...dayItem, isDisabled }); |
||||||
|
}} |
||||||
|
aria-disabled={isDisabled} |
||||||
|
aria-label={dayLabel} |
||||||
|
aria-selected={ |
||||||
|
isSelected || isStartingDayRange || isEndingDayRange || isWithinRange |
||||||
|
} |
||||||
|
{...(!isStandard || !isOnActiveSlide || isQuickSelectorOpen |
||||||
|
? { 'aria-hidden': true } |
||||||
|
: {})} |
||||||
|
role="gridcell" |
||||||
|
data-is-default-selectable={shouldEnableKeyboardNavigation} |
||||||
|
> |
||||||
|
<p>{!isStandard ? '' : getLanguageDigits(day)}</p> |
||||||
|
{/* Dots Of Events */} |
||||||
|
<span>{specialDate(day, month, year)}</span> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const renderMonthDays = (isInitialActiveChild) => { |
||||||
|
const date = getSlideDate({ |
||||||
|
activeDate, |
||||||
|
isInitialActiveChild, |
||||||
|
monthChangeDirection, |
||||||
|
parent: calendarSectionWrapper.current, |
||||||
|
}); |
||||||
|
const allDays = getViewMonthDays(date); |
||||||
|
const renderSingleWeekRow = (weekRowIndex) => { |
||||||
|
const eachWeekDays = allDays |
||||||
|
.slice(weekRowIndex * 7, weekRowIndex * 7 + 7) |
||||||
|
.map(renderEachWeekDays); |
||||||
|
return ( |
||||||
|
<div |
||||||
|
key={String(weekRowIndex)} |
||||||
|
className="Calendar__weekRow" |
||||||
|
role="row" |
||||||
|
> |
||||||
|
{eachWeekDays} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
return Array.from(Array(6).keys()).map(renderSingleWeekRow); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleKeyDown = (e) => { |
||||||
|
handleKeyboardNavigation(e, { allowVerticalArrows: true }); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
ref={calendarSectionWrapper} |
||||||
|
className="Calendar__sectionWrapper" |
||||||
|
role="presentation" |
||||||
|
data-testid="days-section-wrapper" |
||||||
|
onKeyDown={handleKeyDown} |
||||||
|
> |
||||||
|
<div |
||||||
|
onAnimationEnd={(e) => { |
||||||
|
handleSlideAnimationEnd(e); |
||||||
|
onSlideChange(); |
||||||
|
}} |
||||||
|
className="Calendar__section -shown" |
||||||
|
role="rowgroup" |
||||||
|
> |
||||||
|
{renderMonthDays(true)} |
||||||
|
</div> |
||||||
|
<div |
||||||
|
onAnimationEnd={(e) => { |
||||||
|
handleSlideAnimationEnd(e); |
||||||
|
onSlideChange(); |
||||||
|
}} |
||||||
|
className="Calendar__section -hiddenNext" |
||||||
|
role="rowgroup" |
||||||
|
> |
||||||
|
{renderMonthDays(false)} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
DaysList.defaultProps = { |
||||||
|
onChange: () => { }, |
||||||
|
onDisabledDayError: () => { }, |
||||||
|
disabledDays: [], |
||||||
|
calendarTodayClassName: '', |
||||||
|
calendarSelectedDayClassName: '', |
||||||
|
calendarRangeStartClassName: '', |
||||||
|
calendarRangeBetweenClassName: '', |
||||||
|
calendarRangeEndClassName: '', |
||||||
|
shouldHighlightWeekends: false, |
||||||
|
}; |
||||||
|
|
||||||
|
export default DaysList; |
@ -0,0 +1,205 @@ |
|||||||
|
import React, { useEffect, useRef } from 'react'; |
||||||
|
|
||||||
|
import { isSameDay } from '../shared/generalUtils'; |
||||||
|
import { getSlideDate, animateContent, handleSlideAnimationEnd } from '../shared/sliderHelpers'; |
||||||
|
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks'; |
||||||
|
|
||||||
|
const Header = ({ |
||||||
|
maximumDate, |
||||||
|
minimumDate, |
||||||
|
onMonthChange, |
||||||
|
activeDate, |
||||||
|
monthChangeDirection, |
||||||
|
onMonthSelect, |
||||||
|
onYearSelect, |
||||||
|
isMonthSelectorOpen, |
||||||
|
isYearSelectorOpen, |
||||||
|
locale, |
||||||
|
type |
||||||
|
}) => { |
||||||
|
const headerElement = useRef(null); |
||||||
|
const monthYearWrapperElement = useRef(null); |
||||||
|
|
||||||
|
const { getMonthName, isBeforeDate, getLanguageDigits } = useLocaleUtils(locale); |
||||||
|
const { |
||||||
|
isRtl, |
||||||
|
nextMonth, |
||||||
|
previousMonth, |
||||||
|
openMonthSelector, |
||||||
|
closeMonthSelector, |
||||||
|
openYearSelector, |
||||||
|
closeYearSelector, |
||||||
|
} = useLocaleLanguage(locale); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!monthChangeDirection) return; |
||||||
|
animateContent({ |
||||||
|
direction: monthChangeDirection, |
||||||
|
parent: monthYearWrapperElement.current, |
||||||
|
}); |
||||||
|
}, [monthChangeDirection]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const isOpen = isMonthSelectorOpen || isYearSelectorOpen; |
||||||
|
const monthText = headerElement.current.querySelector( |
||||||
|
'.Calendar__monthYear.-shown .Calendar__monthText', |
||||||
|
); |
||||||
|
const yearText = monthText.nextSibling; |
||||||
|
const hasActiveBackground = element => element.classList.contains('-activeBackground'); |
||||||
|
const isInitialRender = |
||||||
|
!isOpen && !hasActiveBackground(monthText) && !hasActiveBackground(yearText); |
||||||
|
if (isInitialRender) return; |
||||||
|
|
||||||
|
const arrows = [...headerElement.current.querySelectorAll('.Calendar__monthArrowWrapper')]; |
||||||
|
const hasMonthSelectorToggled = isMonthSelectorOpen || hasActiveBackground(monthText); |
||||||
|
const primaryElement = hasMonthSelectorToggled ? monthText : yearText; |
||||||
|
const secondaryElement = hasMonthSelectorToggled ? yearText : monthText; |
||||||
|
|
||||||
|
let translateXDirection = hasMonthSelectorToggled ? 1 : -1; |
||||||
|
if (isRtl) translateXDirection *= -1; |
||||||
|
const scale = !isOpen ? 0.95 : 1; |
||||||
|
const translateX = !isOpen ? 0 : `${(translateXDirection * secondaryElement.offsetWidth) / 2}`; |
||||||
|
if (!isOpen) { |
||||||
|
secondaryElement.removeAttribute('aria-hidden'); |
||||||
|
} else { |
||||||
|
secondaryElement.setAttribute('aria-hidden', true); |
||||||
|
} |
||||||
|
secondaryElement.setAttribute('tabindex', isOpen ? '-1' : '0'); |
||||||
|
secondaryElement.style.transform = ''; |
||||||
|
primaryElement.style.transform = `scale(${scale}) ${ |
||||||
|
translateX ? `translateX(${translateX}px)` : '' |
||||||
|
}`;
|
||||||
|
primaryElement.classList.toggle('-activeBackground'); |
||||||
|
secondaryElement.classList.toggle('-hidden'); |
||||||
|
arrows.forEach(arrow => { |
||||||
|
const isHidden = arrow.classList.contains('-hidden'); |
||||||
|
arrow.classList.toggle('-hidden'); |
||||||
|
if (isHidden) { |
||||||
|
arrow.removeAttribute('aria-hidden'); |
||||||
|
arrow.setAttribute('tabindex', '0'); |
||||||
|
} else { |
||||||
|
arrow.setAttribute('aria-hidden', true); |
||||||
|
arrow.setAttribute('tabindex', '-1'); |
||||||
|
} |
||||||
|
}); |
||||||
|
}, [isMonthSelectorOpen, isYearSelectorOpen]); |
||||||
|
|
||||||
|
const getMonthYearText = isInitialActiveChild => { |
||||||
|
const date = getSlideDate({ |
||||||
|
isInitialActiveChild, |
||||||
|
monthChangeDirection, |
||||||
|
activeDate, |
||||||
|
parent: monthYearWrapperElement.current, |
||||||
|
}); |
||||||
|
const year = getLanguageDigits(date.year); |
||||||
|
const month = getMonthName(date.month); |
||||||
|
return { month, year }; |
||||||
|
}; |
||||||
|
|
||||||
|
const isNextMonthArrowDisabled = |
||||||
|
maximumDate && |
||||||
|
isBeforeDate(maximumDate, { ...activeDate, month: activeDate.month + 1, day: 1 }); |
||||||
|
const isPreviousMonthArrowDisabled = |
||||||
|
minimumDate && |
||||||
|
(isBeforeDate({ ...activeDate, day: 1 }, minimumDate) || |
||||||
|
isSameDay(minimumDate, { ...activeDate, day: 1 })); |
||||||
|
|
||||||
|
const onMonthChangeTrigger = direction => { |
||||||
|
const isMonthChanging = Array.from(monthYearWrapperElement.current.children).some(child => |
||||||
|
child.classList.contains('-shownAnimated'), |
||||||
|
); |
||||||
|
if (isMonthChanging) return; |
||||||
|
onMonthChange(direction); |
||||||
|
}; |
||||||
|
|
||||||
|
// first button text is the one who shows the current month and year(initial active child)
|
||||||
|
const monthYearButtons = [true, false].map(isInitialActiveChild => { |
||||||
|
const { month, year } = getMonthYearText(isInitialActiveChild); |
||||||
|
const isActiveMonth = month === getMonthName(activeDate.month); |
||||||
|
const hiddenStatus = { |
||||||
|
...(isActiveMonth ? {} : { 'aria-hidden': true }), |
||||||
|
}; |
||||||
|
return ( |
||||||
|
<div |
||||||
|
onAnimationEnd={handleSlideAnimationEnd} |
||||||
|
className={`Calendar__monthYear ${isInitialActiveChild ? '-shown' : '-hiddenNext'}`} |
||||||
|
role="presentation" |
||||||
|
key={String(isInitialActiveChild)} |
||||||
|
{...hiddenStatus} |
||||||
|
> |
||||||
|
<button |
||||||
|
onClick={onMonthSelect} |
||||||
|
type="button" |
||||||
|
className="Calendar__monthText" |
||||||
|
aria-label={isMonthSelectorOpen ? closeMonthSelector : openMonthSelector} |
||||||
|
tabIndex={isActiveMonth ? '0' : '-1'} |
||||||
|
{...hiddenStatus} |
||||||
|
> |
||||||
|
{month} |
||||||
|
</button> |
||||||
|
<button |
||||||
|
onClick={onYearSelect} |
||||||
|
type="button" |
||||||
|
className="Calendar__yearText" |
||||||
|
aria-label={isYearSelectorOpen ? closeYearSelector : openYearSelector} |
||||||
|
tabIndex={isActiveMonth ? '0' : '-1'} |
||||||
|
{...hiddenStatus} |
||||||
|
> |
||||||
|
{year} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div ref={headerElement} className={`Calendar__header ${type}`}> |
||||||
|
{type == 'dashboard' ?
|
||||||
|
|
||||||
|
<div |
||||||
|
className={`Calendar__monthYearContainer ${type}`} |
||||||
|
ref={monthYearWrapperElement} |
||||||
|
data-testid="month-year-container" |
||||||
|
> |
||||||
|
|
||||||
|
{monthYearButtons} |
||||||
|
</div> |
||||||
|
: |
||||||
|
<> |
||||||
|
<button |
||||||
|
className="Calendar__monthArrowWrapper -right" |
||||||
|
onClick={() => { |
||||||
|
onMonthChangeTrigger('PREVIOUS'); |
||||||
|
}} |
||||||
|
aria-label={previousMonth} |
||||||
|
type="button" |
||||||
|
disabled={isPreviousMonthArrowDisabled} |
||||||
|
> |
||||||
|
<span className="Calendar__monthArrow" /> |
||||||
|
</button> |
||||||
|
<div |
||||||
|
className="Calendar__monthYearContainer" |
||||||
|
ref={monthYearWrapperElement} |
||||||
|
data-testid="month-year-container" |
||||||
|
> |
||||||
|
|
||||||
|
{monthYearButtons} |
||||||
|
</div> |
||||||
|
<button |
||||||
|
className="Calendar__monthArrowWrapper -left" |
||||||
|
onClick={() => { |
||||||
|
onMonthChangeTrigger('NEXT'); |
||||||
|
}} |
||||||
|
aria-label={nextMonth} |
||||||
|
type="button" |
||||||
|
disabled={isNextMonthArrowDisabled} |
||||||
|
> |
||||||
|
<span className="Calendar__monthArrow" /> |
||||||
|
</button> |
||||||
|
</> |
||||||
|
|
||||||
|
} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Header; |
@ -0,0 +1,74 @@ |
|||||||
|
import React, { useRef, useEffect } from 'react'; |
||||||
|
|
||||||
|
import { isSameDay } from '../shared/generalUtils'; |
||||||
|
import handleKeyboardNavigation from '../shared/keyboardNavigation'; |
||||||
|
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks'; |
||||||
|
|
||||||
|
const MonthSelector = ({ activeDate, maximumDate, minimumDate, onMonthSelect, isOpen, locale }) => { |
||||||
|
const monthSelector = useRef(null); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const classToggleMethod = isOpen ? 'add' : 'remove'; |
||||||
|
monthSelector.current.classList[classToggleMethod]('-open'); |
||||||
|
}, [isOpen]); |
||||||
|
|
||||||
|
const { getMonthNumber, isBeforeDate } = useLocaleUtils(locale); |
||||||
|
const { months: monthsList } = useLocaleLanguage(locale); |
||||||
|
|
||||||
|
const handleKeyDown = e => { |
||||||
|
handleKeyboardNavigation(e, { allowVerticalArrows: false }); |
||||||
|
}; |
||||||
|
|
||||||
|
const renderMonthSelectorItems = () => |
||||||
|
monthsList.map(persianMonth => { |
||||||
|
const monthNumber = getMonthNumber(persianMonth); |
||||||
|
const monthDate = { day: 1, month: monthNumber, year: activeDate.year }; |
||||||
|
const isAfterMaximumDate = |
||||||
|
maximumDate && isBeforeDate(maximumDate, { ...monthDate, month: monthNumber }); |
||||||
|
const isBeforeMinimumDate = |
||||||
|
minimumDate && |
||||||
|
(isBeforeDate({ ...monthDate, month: monthNumber + 1 }, minimumDate) || |
||||||
|
isSameDay({ ...monthDate, month: monthNumber + 1 }, minimumDate)); |
||||||
|
const isSelected = monthNumber === activeDate.month; |
||||||
|
return ( |
||||||
|
<li |
||||||
|
key={persianMonth} |
||||||
|
className={`Calendar__monthSelectorItem ${isSelected ? '-active' : ''}`} |
||||||
|
> |
||||||
|
<button |
||||||
|
tabIndex={isSelected && isOpen ? '0' : '-1'} |
||||||
|
onClick={() => { |
||||||
|
onMonthSelect(monthNumber); |
||||||
|
}} |
||||||
|
className="Calendar__monthSelectorItemText" |
||||||
|
type="button" |
||||||
|
disabled={isAfterMaximumDate || isBeforeMinimumDate} |
||||||
|
aria-pressed={isSelected} |
||||||
|
data-is-default-selectable={isSelected} |
||||||
|
> |
||||||
|
{persianMonth} |
||||||
|
</button> |
||||||
|
</li> |
||||||
|
); |
||||||
|
}); |
||||||
|
return ( |
||||||
|
<div |
||||||
|
role="presentation" |
||||||
|
className="Calendar__monthSelectorAnimationWrapper" |
||||||
|
{...(isOpen ? {} : { 'aria-hidden': true })} |
||||||
|
> |
||||||
|
<div |
||||||
|
role="presentation" |
||||||
|
data-testid="month-selector-wrapper" |
||||||
|
className="Calendar__monthSelectorWrapper" |
||||||
|
onKeyDown={handleKeyDown} |
||||||
|
> |
||||||
|
<ul ref={monthSelector} className="Calendar__monthSelector" data-testid="month-selector"> |
||||||
|
{renderMonthSelectorItems()} |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default MonthSelector; |
@ -0,0 +1,99 @@ |
|||||||
|
import React, { useRef, useEffect } from 'react'; |
||||||
|
|
||||||
|
import { MINIMUM_SELECTABLE_YEAR_SUBTRACT, MAXIMUM_SELECTABLE_YEAR_SUM } from '../shared/constants'; |
||||||
|
import handleKeyboardNavigation from '../shared/keyboardNavigation'; |
||||||
|
import { useLocaleUtils } from '../shared/hooks'; |
||||||
|
|
||||||
|
const YearSelector = ({ |
||||||
|
isOpen, |
||||||
|
activeDate, |
||||||
|
onYearSelect, |
||||||
|
selectorStartingYear, |
||||||
|
selectorEndingYear, |
||||||
|
maximumDate, |
||||||
|
minimumDate, |
||||||
|
locale, |
||||||
|
}) => { |
||||||
|
const wrapperElement = useRef(null); |
||||||
|
const yearListElement = useRef(null); |
||||||
|
|
||||||
|
const { getLanguageDigits, getToday } = useLocaleUtils(locale); |
||||||
|
const startingYearValue = |
||||||
|
selectorStartingYear || getToday().year - MINIMUM_SELECTABLE_YEAR_SUBTRACT; |
||||||
|
const endingYearValue = selectorEndingYear || getToday().year + MAXIMUM_SELECTABLE_YEAR_SUM; |
||||||
|
const allYears = []; |
||||||
|
for (let i = startingYearValue; i <= endingYearValue; i += 1) { |
||||||
|
allYears.push(i); |
||||||
|
} |
||||||
|
useEffect(() => { |
||||||
|
const classToggleMethod = isOpen ? 'add' : 'remove'; |
||||||
|
const activeSelectorYear = wrapperElement.current.querySelector( |
||||||
|
'.Calendar__yearSelectorItem.-active', |
||||||
|
); |
||||||
|
if (!activeSelectorYear) { |
||||||
|
throw new RangeError( |
||||||
|
`Provided value for year is out of selectable year range. You're probably using a wrong locale prop value or your provided value's locale is different from the date picker locale. Try changing the 'locale' prop or the value you've provided.`, |
||||||
|
); |
||||||
|
} |
||||||
|
wrapperElement.current.classList[classToggleMethod]('-faded'); |
||||||
|
yearListElement.current.scrollTop = |
||||||
|
activeSelectorYear.offsetTop - activeSelectorYear.offsetHeight * 5; |
||||||
|
yearListElement.current.classList[classToggleMethod]('-open'); |
||||||
|
}, [isOpen]); |
||||||
|
|
||||||
|
const renderSelectorYears = () => { |
||||||
|
return allYears.map(item => { |
||||||
|
const isAfterMaximumDate = maximumDate && item > maximumDate.year; |
||||||
|
const isBeforeMinimumDate = minimumDate && item < minimumDate.year; |
||||||
|
const isSelected = activeDate.year === item; |
||||||
|
return ( |
||||||
|
<li key={item} className={`Calendar__yearSelectorItem ${isSelected ? '-active' : ''}`}> |
||||||
|
<button |
||||||
|
tabIndex={isSelected && isOpen ? '0' : '-1'} |
||||||
|
className="Calendar__yearSelectorText" |
||||||
|
type="button" |
||||||
|
onClick={() => { |
||||||
|
onYearSelect(item); |
||||||
|
}} |
||||||
|
disabled={isAfterMaximumDate || isBeforeMinimumDate} |
||||||
|
aria-pressed={isSelected} |
||||||
|
data-is-default-selectable={isSelected} |
||||||
|
> |
||||||
|
{getLanguageDigits(item)} |
||||||
|
</button> |
||||||
|
</li> |
||||||
|
); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleKeyDown = e => { |
||||||
|
handleKeyboardNavigation(e, { allowVerticalArrows: false }); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className="Calendar__yearSelectorAnimationWrapper" |
||||||
|
role="presentation" |
||||||
|
{...(isOpen ? {} : { 'aria-hidden': true })} |
||||||
|
> |
||||||
|
<div |
||||||
|
ref={wrapperElement} |
||||||
|
className="Calendar__yearSelectorWrapper" |
||||||
|
role="presentation" |
||||||
|
data-testid="year-selector-wrapper" |
||||||
|
onKeyDown={handleKeyDown} |
||||||
|
> |
||||||
|
<ul ref={yearListElement} className="Calendar__yearSelector" data-testid="year-selector"> |
||||||
|
{renderSelectorYears()} |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
YearSelector.defaultProps = { |
||||||
|
selectorStartingYear: 0, |
||||||
|
selectorEndingYear: 0, |
||||||
|
}; |
||||||
|
|
||||||
|
export default YearSelector; |
@ -0,0 +1,4 @@ |
|||||||
|
export { default as Header } from './Header'; |
||||||
|
export { default as MonthSelector } from './MonthSelector'; |
||||||
|
export { default as YearSelector } from './YearSelector'; |
||||||
|
export { default as DaysList } from './DaysList'; |
@ -0,0 +1,3 @@ |
|||||||
|
export { default } from "./DatePicker"; |
||||||
|
export * from "./Calendar"; |
||||||
|
export { default as utils } from "./shared/localeUtils"; |
@ -0,0 +1,104 @@ |
|||||||
|
export const PERSIAN_NUMBERS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; |
||||||
|
|
||||||
|
export const PERSIAN_MONTHS = [ |
||||||
|
'فروردین', |
||||||
|
'اردیبهشت', |
||||||
|
'خرداد', |
||||||
|
'تیر', |
||||||
|
'مرداد', |
||||||
|
'شهریور', |
||||||
|
'مهر', |
||||||
|
'آبان', |
||||||
|
'آذر', |
||||||
|
'دی', |
||||||
|
'بهمن', |
||||||
|
'اسفند', |
||||||
|
]; |
||||||
|
|
||||||
|
export const GREGORIAN_MONTHS = [ |
||||||
|
'January', |
||||||
|
'February', |
||||||
|
'March', |
||||||
|
'April', |
||||||
|
'May', |
||||||
|
'June', |
||||||
|
'July', |
||||||
|
'August', |
||||||
|
'September', |
||||||
|
'October', |
||||||
|
'November', |
||||||
|
'December', |
||||||
|
]; |
||||||
|
|
||||||
|
export const PERSIAN_WEEK_DAYS = [ |
||||||
|
{ |
||||||
|
name: 'شنبه', |
||||||
|
short: 'ش', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'یکشنبه', |
||||||
|
short: 'ی', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'دوشنبه', |
||||||
|
short: 'د', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'سه شنبه', |
||||||
|
short: 'س', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'چهارشنبه', |
||||||
|
short: 'چ', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'پنجشنبه', |
||||||
|
short: 'پ', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'جمعه', |
||||||
|
short: 'ج', |
||||||
|
isWeekend: true, |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
export const GREGORIAN_WEEK_DAYS = [ |
||||||
|
{ |
||||||
|
name: 'Sunday', |
||||||
|
short: 'S', |
||||||
|
isWeekend: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'Monday', |
||||||
|
short: 'M', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'Tuesday', |
||||||
|
short: 'T', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'Wednesday', |
||||||
|
short: 'W', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'Thursday', |
||||||
|
short: 'T', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'Friday', |
||||||
|
short: 'F', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'Saturday', |
||||||
|
short: 'S', |
||||||
|
isWeekend: true, |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
export const MINIMUM_SELECTABLE_YEAR_SUBTRACT = 100; |
||||||
|
|
||||||
|
export const MAXIMUM_SELECTABLE_YEAR_SUM = 50; |
||||||
|
|
||||||
|
export const TYPE_SINGLE_DATE = 'SINGLE_DATE'; |
||||||
|
export const TYPE_RANGE = 'RANGE'; |
||||||
|
export const TYPE_MUTLI_DATE = 'MUTLI_DATE'; |
@ -0,0 +1,69 @@ |
|||||||
|
import { TYPE_SINGLE_DATE, TYPE_RANGE, TYPE_MUTLI_DATE } from './constants'; |
||||||
|
|
||||||
|
/* |
||||||
|
These utility functions don't depend on locale of the date picker(Persian or Gregorian) |
||||||
|
*/ |
||||||
|
|
||||||
|
const createUniqueRange = (number, startingId) => |
||||||
|
Array.from(Array(number).keys()).map(key => ({ |
||||||
|
value: key + 1, |
||||||
|
id: `${startingId}-${key}`, |
||||||
|
})); |
||||||
|
|
||||||
|
const isSameDay = (day1, day2) => { |
||||||
|
if (!day1 || !day2) return false; |
||||||
|
return day1.day === day2.day && day1.month === day2.month && day1.year === day2.year; |
||||||
|
}; |
||||||
|
|
||||||
|
const putZero = number => (number.toString().length === 1 ? `0${number}` : number); |
||||||
|
|
||||||
|
const toExtendedDay = date => [date.year, date.month, date.day]; |
||||||
|
|
||||||
|
const shallowClone = value => ({ ...value }); |
||||||
|
|
||||||
|
const deepCloneObject = obj => |
||||||
|
JSON.parse(JSON.stringify(obj, (key, value) => (typeof value === 'undefined' ? null : value))); |
||||||
|
|
||||||
|
const getDateAccordingToMonth = (date, direction) => { |
||||||
|
const toSum = direction === 'NEXT' ? 1 : -1; |
||||||
|
let newMonthIndex = date.month + toSum; |
||||||
|
let newYear = date.year; |
||||||
|
if (newMonthIndex < 1) { |
||||||
|
newMonthIndex = 12; |
||||||
|
newYear -= 1; |
||||||
|
} |
||||||
|
if (newMonthIndex > 12) { |
||||||
|
newMonthIndex = 1; |
||||||
|
newYear += 1; |
||||||
|
} |
||||||
|
const newDate = { year: newYear, month: newMonthIndex, day: 1 }; |
||||||
|
return newDate; |
||||||
|
}; |
||||||
|
|
||||||
|
const hasProperty = (object, propertyName) => |
||||||
|
Object.prototype.hasOwnProperty.call(object || {}, propertyName); |
||||||
|
|
||||||
|
const getValueType = value => { |
||||||
|
if (Array.isArray(value)) return TYPE_MUTLI_DATE; |
||||||
|
if (hasProperty(value, 'from') && hasProperty(value, 'to')) return TYPE_RANGE; |
||||||
|
if ( |
||||||
|
!value || |
||||||
|
(hasProperty(value, 'year') && hasProperty(value, 'month') && hasProperty(value, 'day')) |
||||||
|
) { |
||||||
|
return TYPE_SINGLE_DATE; |
||||||
|
} |
||||||
|
throw new TypeError( |
||||||
|
`The passed value is malformed! Please make sure you're using one of the valid value types for date picker.`, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export { |
||||||
|
createUniqueRange, |
||||||
|
isSameDay, |
||||||
|
putZero, |
||||||
|
toExtendedDay, |
||||||
|
shallowClone, |
||||||
|
deepCloneObject, |
||||||
|
getDateAccordingToMonth, |
||||||
|
getValueType, |
||||||
|
}; |
@ -0,0 +1,10 @@ |
|||||||
|
import { useMemo } from 'react'; |
||||||
|
|
||||||
|
import utils from './localeUtils'; |
||||||
|
import getLanguageText from './localeLanguages'; |
||||||
|
|
||||||
|
const useLocaleUtils = locale => useMemo(() => utils(locale), [locale]); |
||||||
|
|
||||||
|
const useLocaleLanguage = locale => useMemo(() => getLanguageText(locale), [locale]); |
||||||
|
|
||||||
|
export { useLocaleUtils, useLocaleLanguage }; |
@ -0,0 +1,44 @@ |
|||||||
|
const handleArrowKeys = (e, { allowVerticalArrows }) => { |
||||||
|
const { activeElement } = document; |
||||||
|
const getNthChildSafe = (element, index) => (element ? element.children[index] : null); |
||||||
|
const getStandardItem = item => item && (item.hasAttribute('aria-hidden') ? null : item); |
||||||
|
const { nextSibling: nextRow, previousSibling: previousRow } = activeElement.parentElement; |
||||||
|
const nextSibling = getStandardItem(activeElement.nextSibling || getNthChildSafe(nextRow, 0)); |
||||||
|
const previousRowLength = previousRow ? previousRow.children.length - 1 : 0; |
||||||
|
const previousSibling = getStandardItem( |
||||||
|
activeElement.previousSibling || getNthChildSafe(previousRow, previousRowLength), |
||||||
|
); |
||||||
|
const getVerticalSibling = row => |
||||||
|
getNthChildSafe(row, Array.from(activeElement.parentElement.children).indexOf(activeElement)); |
||||||
|
const downSibling = getStandardItem(getVerticalSibling(nextRow)); |
||||||
|
const upSibling = getStandardItem(getVerticalSibling(previousRow)); |
||||||
|
const isDefaultSelectable = activeElement.dataset.isDefaultSelectable === 'true'; |
||||||
|
|
||||||
|
if (!isDefaultSelectable) activeElement.tabIndex = '-1'; |
||||||
|
const focusIfAvailable = element => { |
||||||
|
e.preventDefault(); |
||||||
|
/* istanbul ignore else */ |
||||||
|
if (element) { |
||||||
|
element.setAttribute('tabindex', '0'); |
||||||
|
element.focus(); |
||||||
|
} |
||||||
|
}; |
||||||
|
switch (e.key) { |
||||||
|
case 'ArrowRight': |
||||||
|
focusIfAvailable(nextSibling); |
||||||
|
break; |
||||||
|
case 'ArrowLeft': |
||||||
|
focusIfAvailable(previousSibling); |
||||||
|
break; |
||||||
|
case 'ArrowDown': |
||||||
|
/* istanbul ignore else */ |
||||||
|
if (allowVerticalArrows) focusIfAvailable(downSibling); |
||||||
|
break; |
||||||
|
case 'ArrowUp': |
||||||
|
/* istanbul ignore else */ |
||||||
|
if (allowVerticalArrows) focusIfAvailable(upSibling); |
||||||
|
break; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export default handleArrowKeys; |
@ -0,0 +1,85 @@ |
|||||||
|
import jalaali from 'jalaali-js'; |
||||||
|
|
||||||
|
import { |
||||||
|
GREGORIAN_MONTHS, |
||||||
|
PERSIAN_MONTHS, |
||||||
|
GREGORIAN_WEEK_DAYS, |
||||||
|
PERSIAN_WEEK_DAYS, |
||||||
|
PERSIAN_NUMBERS, |
||||||
|
} from './constants'; |
||||||
|
import { toExtendedDay } from './generalUtils'; |
||||||
|
|
||||||
|
const localeLanguages = { |
||||||
|
en: { |
||||||
|
months: GREGORIAN_MONTHS, |
||||||
|
weekDays: GREGORIAN_WEEK_DAYS, |
||||||
|
weekStartingIndex: 0, |
||||||
|
getToday(gregorainTodayObject) { |
||||||
|
return gregorainTodayObject; |
||||||
|
}, |
||||||
|
toNativeDate(date) { |
||||||
|
return new Date(date.year, date.month - 1, date.day); |
||||||
|
}, |
||||||
|
getMonthLength(date) { |
||||||
|
return new Date(date.year, date.month, 0).getDate(); |
||||||
|
}, |
||||||
|
transformDigit(digit) { |
||||||
|
return digit; |
||||||
|
}, |
||||||
|
nextMonth: 'Next Month', |
||||||
|
previousMonth: 'Previous Month', |
||||||
|
openMonthSelector: 'Open Month Selector', |
||||||
|
openYearSelector: 'Open Year Selector', |
||||||
|
closeMonthSelector: 'Close Month Selector', |
||||||
|
closeYearSelector: 'Close Year Selector', |
||||||
|
from: 'from', |
||||||
|
to: 'to', |
||||||
|
defaultPlaceholder: 'Select...', |
||||||
|
digitSeparator: ',', |
||||||
|
yearLetterSkip: 0, |
||||||
|
isRtl: false, |
||||||
|
}, |
||||||
|
fa: { |
||||||
|
months: PERSIAN_MONTHS, |
||||||
|
weekDays: PERSIAN_WEEK_DAYS, |
||||||
|
weekStartingIndex: 1, |
||||||
|
getToday({ year, month, day }) { |
||||||
|
const { jy, jm, jd } = jalaali.toJalaali(year, month, day); |
||||||
|
return { year: jy, month: jm, day: jd }; |
||||||
|
}, |
||||||
|
toNativeDate(date) { |
||||||
|
const gregorian = jalaali.toGregorian(...toExtendedDay(date)); |
||||||
|
return new Date(gregorian.gy, gregorian.gm - 1, gregorian.gd); |
||||||
|
}, |
||||||
|
getMonthLength(date) { |
||||||
|
return jalaali.jalaaliMonthLength(date.year, date.month); |
||||||
|
}, |
||||||
|
transformDigit(digit) { |
||||||
|
return digit |
||||||
|
.toString() |
||||||
|
.split('') |
||||||
|
.map(letter => PERSIAN_NUMBERS[Number(letter)]) |
||||||
|
.join(''); |
||||||
|
}, |
||||||
|
nextMonth: 'ماه بعد', |
||||||
|
previousMonth: 'ماه قبل', |
||||||
|
openMonthSelector: 'نمایش انتخابگر ماه', |
||||||
|
openYearSelector: 'نمایش انتخابگر سال', |
||||||
|
closeMonthSelector: 'بستن انتخابگر ماه', |
||||||
|
closeYearSelector: 'بستن انتخابگر ماه', |
||||||
|
from: 'از', |
||||||
|
to: 'تا', |
||||||
|
defaultPlaceholder: 'انتخاب...', |
||||||
|
digitSeparator: '،', |
||||||
|
yearLetterSkip: -2, |
||||||
|
isRtl: true, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const getLocaleDetails = locale => { |
||||||
|
if (typeof locale === 'string') return localeLanguages[locale]; |
||||||
|
return locale; |
||||||
|
}; |
||||||
|
|
||||||
|
export { localeLanguages }; |
||||||
|
export default getLocaleDetails; |
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
These utility functions highly depend on locale of the date picker(Persian or Gregorian) |
||||||
|
*/ |
||||||
|
|
||||||
|
import getLocaleDetails from './localeLanguages'; |
||||||
|
|
||||||
|
const utils = (locale = 'en') => { |
||||||
|
const { |
||||||
|
months: monthsList, |
||||||
|
getToday: localeGetToday, |
||||||
|
toNativeDate, |
||||||
|
getMonthLength, |
||||||
|
weekStartingIndex, |
||||||
|
transformDigit: getLanguageDigits, |
||||||
|
} = typeof locale === 'string' ? getLocaleDetails(locale) : locale; |
||||||
|
|
||||||
|
const getToday = () => { |
||||||
|
const todayDate = new Date(); |
||||||
|
const year = todayDate.getFullYear(); |
||||||
|
const month = todayDate.getMonth() + 1; |
||||||
|
const day = todayDate.getDate(); |
||||||
|
return localeGetToday({ year, month, day }); |
||||||
|
}; |
||||||
|
|
||||||
|
const getMonthName = month => monthsList[month - 1]; |
||||||
|
|
||||||
|
const getMonthNumber = monthName => monthsList.indexOf(monthName) + 1; |
||||||
|
|
||||||
|
const getMonthFirstWeekday = date => { |
||||||
|
const gregorianDate = toNativeDate({ ...date, day: 1 }); |
||||||
|
const weekday = gregorianDate.getDay(); |
||||||
|
const dayIndex = weekday + weekStartingIndex; |
||||||
|
return dayIndex % 7; |
||||||
|
}; |
||||||
|
|
||||||
|
const isBeforeDate = (day1, day2) => { |
||||||
|
if (!day1 || !day2) return false; |
||||||
|
return toNativeDate(day1) < toNativeDate(day2); |
||||||
|
}; |
||||||
|
|
||||||
|
const checkDayInDayRange = ({ day, from, to }) => { |
||||||
|
if (!day || !from || !to) return false; |
||||||
|
const nativeDay = toNativeDate(day); |
||||||
|
const nativeFrom = toNativeDate(from); |
||||||
|
const nativeTo = toNativeDate(to); |
||||||
|
return nativeDay > nativeFrom && nativeDay < nativeTo; |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
getToday, |
||||||
|
getMonthName, |
||||||
|
getMonthNumber, |
||||||
|
getMonthLength, |
||||||
|
getMonthFirstWeekday, |
||||||
|
isBeforeDate, |
||||||
|
checkDayInDayRange, |
||||||
|
getLanguageDigits, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export default utils; |
@ -0,0 +1,33 @@ |
|||||||
|
import { getDateAccordingToMonth } from './generalUtils'; |
||||||
|
|
||||||
|
const getSlideDate = ({ parent, isInitialActiveChild, activeDate, monthChangeDirection }) => { |
||||||
|
if (!parent) { |
||||||
|
return isInitialActiveChild ? activeDate : getDateAccordingToMonth(activeDate, 'NEXT'); |
||||||
|
} |
||||||
|
const child = parent.children[isInitialActiveChild ? 0 : 1]; |
||||||
|
const isActiveSlide = |
||||||
|
child.classList.contains('-shown') || child.classList.contains('-shownAnimated'); // check -shownAnimated for Safari bug
|
||||||
|
return isActiveSlide ? activeDate : getDateAccordingToMonth(activeDate, monthChangeDirection); |
||||||
|
}; |
||||||
|
|
||||||
|
const animateContent = ({ parent, direction }) => { |
||||||
|
const wrapperChildren = Array.from(parent.children); |
||||||
|
const shownItem = wrapperChildren.find(child => child.classList.contains('-shown')); |
||||||
|
const hiddenItem = wrapperChildren.find(child => child !== shownItem); |
||||||
|
const baseClass = shownItem.classList[0]; |
||||||
|
const isNextMonth = direction === 'NEXT'; |
||||||
|
const getAnimationClass = value => (value ? '-hiddenNext' : '-hiddenPrevious'); |
||||||
|
hiddenItem.style.transition = 'none'; |
||||||
|
shownItem.style.transition = ''; |
||||||
|
shownItem.className = `${baseClass} ${getAnimationClass(!isNextMonth)}`; |
||||||
|
hiddenItem.className = `${baseClass} ${getAnimationClass(isNextMonth)}`; |
||||||
|
hiddenItem.classList.add('-shownAnimated'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSlideAnimationEnd = ({ target }) => { |
||||||
|
target.classList.remove('-hiddenNext'); |
||||||
|
target.classList.remove('-hiddenPrevious'); |
||||||
|
target.classList.replace('-shownAnimated', '-shown'); |
||||||
|
}; |
||||||
|
|
||||||
|
export { animateContent, getSlideDate, handleSlideAnimationEnd }; |
@ -0,0 +1,48 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
|
||||||
|
function Search({ |
||||||
|
lang, |
||||||
|
textColor, |
||||||
|
backgroundColor = "bg-white ", |
||||||
|
onChange, |
||||||
|
name, |
||||||
|
icon = true, |
||||||
|
placeholder, |
||||||
|
padding = "py-3 lg:py-4 md:py-5", |
||||||
|
}) { |
||||||
|
return ( |
||||||
|
<div className="flex items-center justify-center w-full z-50 overflow-hidden rounded-md"> |
||||||
|
<div className="relative text-gray-600 focus-within:text-gray-400 w-full"> |
||||||
|
{icon ? ( |
||||||
|
<span className="absolute inset-y-0 flex items-center pl-2 left-0"> |
||||||
|
<button |
||||||
|
type="submit" |
||||||
|
className="p-1 focus:outline-none focus:shadow-outline" |
||||||
|
> |
||||||
|
<img src="/icons/search.png" className="w-5 h-5" alt="" /> |
||||||
|
</button> |
||||||
|
</span> |
||||||
|
) : null} |
||||||
|
<input |
||||||
|
type="search" |
||||||
|
name={name} |
||||||
|
className={`z-50 w-full text-xs font-2 ${padding} ${backgroundColor} ${ |
||||||
|
textColor ? textColor : "text-gray1" |
||||||
|
} border border-solid border-surfaceBorder rounded-md pr-3 ${ |
||||||
|
icon ? "pl-14" : "pl-3" |
||||||
|
} outline-none focus:border-primary`}
|
||||||
|
placeholder={placeholder} |
||||||
|
autoComplete="off" |
||||||
|
onChange={(e) => onChange(e.target.value)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({}); |
||||||
|
|
||||||
|
const mapDispatchToProps = {}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Search); |
@ -0,0 +1,37 @@ |
|||||||
|
// it has style inside index.scss ---> // Start SimpleCalendar && end SimpleCalendar
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import DatePicker from "../CalendarComponents"; |
||||||
|
import { utils } from "../CalendarComponents"; |
||||||
|
|
||||||
|
const SimpleDatePicker = ({returnValue,defaultValue,mode}) => { |
||||||
|
const [selectedDay, setSelectedDay] = useState(defaultValue || null); |
||||||
|
const gotoToday = () => { |
||||||
|
setSelectedDay(utils("fa").getToday()); |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if(mode){ |
||||||
|
returnValue(selectedDay, mode); |
||||||
|
} |
||||||
|
else{ |
||||||
|
returnValue(selectedDay); |
||||||
|
} |
||||||
|
// returnValue(selectedDay);
|
||||||
|
}, [selectedDay]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex items-center justify-center w-full"> |
||||||
|
<DatePicker |
||||||
|
value={selectedDay} |
||||||
|
onChange={setSelectedDay} |
||||||
|
inputPlaceholder="انتخاب تاریخ" |
||||||
|
shouldHighlightWeekends |
||||||
|
renderFooter={() => <button onClick={gotoToday}>برو به امروز</button>} |
||||||
|
locale="fa" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default SimpleDatePicker; |
@ -0,0 +1,23 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
const ShowMoreRows = ({ pageSize, setPageSize }) => { |
||||||
|
return ( |
||||||
|
<select |
||||||
|
className="text-white text-sm rounded cursor-pointer mr-2 bg-[#65A9FF]" |
||||||
|
value={pageSize} |
||||||
|
onChange={(e) => setPageSize(Number(e.target.value))} |
||||||
|
> |
||||||
|
{[7, 10, 25, 50].map((pageSize) => ( |
||||||
|
<option |
||||||
|
key={pageSize} |
||||||
|
value={pageSize} |
||||||
|
className=" text-xs text-right ltr" |
||||||
|
> |
||||||
|
نمایش {pageSize} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ShowMoreRows; |
@ -0,0 +1,31 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import ShowMoreRows from "./components/ShowMoreRows"; |
||||||
|
import GoToPage from "../Toolbar/components/GoToPage"; |
||||||
|
|
||||||
|
export const Footer = ({ |
||||||
|
pageIndex, |
||||||
|
gotoPage, |
||||||
|
pageOptions, |
||||||
|
pageSize, |
||||||
|
setPageSize, |
||||||
|
}) => { |
||||||
|
return ( |
||||||
|
<div className="flex items-center gap-6"> |
||||||
|
<GoToPage |
||||||
|
pageIndex={pageIndex} |
||||||
|
gotoPage={gotoPage} |
||||||
|
pageOptions={pageOptions} |
||||||
|
/> |
||||||
|
<ShowMoreRows pageSize={pageSize} setPageSize={setPageSize} /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({}); |
||||||
|
|
||||||
|
const mapDispatchToProps = {}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Footer); |
@ -0,0 +1,185 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import RowDetail from "../../../components/RowDetail"; |
||||||
|
|
||||||
|
const Table = ({ |
||||||
|
getTableProps, |
||||||
|
headerGroups, |
||||||
|
prepareRow, |
||||||
|
filtering, |
||||||
|
tabs, |
||||||
|
setTabs, |
||||||
|
getTableBodyProps, |
||||||
|
columns, |
||||||
|
page, |
||||||
|
rows, |
||||||
|
footerGroups, |
||||||
|
name, |
||||||
|
schema, |
||||||
|
data, |
||||||
|
editor, |
||||||
|
activeOtherTab, |
||||||
|
setActiveOtherTab, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
}) => { |
||||||
|
// const bodyComponent = React.useMemo(() => {
|
||||||
|
// return {
|
||||||
|
// 1: page.map((row, idx) => {
|
||||||
|
// prepareRow(row);
|
||||||
|
// return (
|
||||||
|
// <React.Fragment key={idx}>
|
||||||
|
// <tr
|
||||||
|
// {...row.getRowProps()}
|
||||||
|
// // className={isEven(idx) ? "bg-gray-600" : "bg-white"}
|
||||||
|
// >
|
||||||
|
// {row.cells.map((cell, index) => {
|
||||||
|
// return (
|
||||||
|
// <td
|
||||||
|
// key={index}
|
||||||
|
// {...cell.getCellProps()}
|
||||||
|
// className="p-4 whitespace-nowrap text-sm text-[#00253A]"
|
||||||
|
// role="cell"
|
||||||
|
// >
|
||||||
|
// {cell.render("Cell")}
|
||||||
|
// </td>
|
||||||
|
// );
|
||||||
|
// })}
|
||||||
|
// </tr>
|
||||||
|
// {row.isExpanded ? (
|
||||||
|
// <tr className="w-full">
|
||||||
|
// <td colSpan={headerGroups[0].headers.length}>
|
||||||
|
// <RowDetail
|
||||||
|
// data={data}
|
||||||
|
// schema={schema.filter((e) => e.more)}
|
||||||
|
// setTabs={setTabs}
|
||||||
|
// tabs={tabs}
|
||||||
|
// row={row.original}
|
||||||
|
// name={name}
|
||||||
|
// />
|
||||||
|
// </td>
|
||||||
|
// </tr>
|
||||||
|
// ) : null}
|
||||||
|
// </React.Fragment>
|
||||||
|
// );
|
||||||
|
// }),
|
||||||
|
// 2 : editor
|
||||||
|
// };
|
||||||
|
// },[tabs]);
|
||||||
|
|
||||||
|
return ( |
||||||
|
<table |
||||||
|
{...getTableProps()} |
||||||
|
className="w-full divide-y divide-gray-200 mb-5" |
||||||
|
> |
||||||
|
<thead className="bg-[#f3fff4]"> |
||||||
|
{headerGroups.map((headerGroup, index) => ( |
||||||
|
<tr key={index} {...headerGroup.getHeaderGroupProps()}> |
||||||
|
{headerGroup.headers.map((column) => ( |
||||||
|
<th |
||||||
|
{...column.getHeaderProps(column.getSortByToggleProps())} |
||||||
|
scope="col" |
||||||
|
className="cursor-pointer py-2 px-2 text-right text-sm bg-[#3287B9] text-background text-white" |
||||||
|
> |
||||||
|
{column.render("Header")} |
||||||
|
{/* {column.render("description")} */} |
||||||
|
<span> |
||||||
|
{column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : ""} |
||||||
|
</span> |
||||||
|
</th> |
||||||
|
))} |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</thead> |
||||||
|
{/* filter row */} |
||||||
|
{console.log("...............................o")} |
||||||
|
{filtering ? ( |
||||||
|
<thead className="bg-[#F1F5FE]"> |
||||||
|
{headerGroups.map((headerGroup, index) => ( |
||||||
|
<tr key={index} {...headerGroup.getHeaderGroupProps()}> |
||||||
|
{headerGroup.headers.map((column) => ( |
||||||
|
<> |
||||||
|
<th |
||||||
|
{...column.getHeaderProps()} |
||||||
|
className="py-2 px-2 text-sm text-primaryText" |
||||||
|
> |
||||||
|
{column.canFilter ? column.render("Filter") : null} |
||||||
|
</th> |
||||||
|
</> |
||||||
|
))} |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</thead> |
||||||
|
) : null} |
||||||
|
<tbody |
||||||
|
{...getTableBodyProps()} |
||||||
|
className="w-full divide-y divide-gray-20" |
||||||
|
> |
||||||
|
{page.map((row, idx) => { |
||||||
|
prepareRow(row); |
||||||
|
return ( |
||||||
|
<React.Fragment key={idx}> |
||||||
|
<tr |
||||||
|
{...row.getRowProps()} |
||||||
|
// className={isEven(idx) ? "bg-gray-600" : "bg-white"}
|
||||||
|
> |
||||||
|
{row.cells.map((cell, index) => { |
||||||
|
return ( |
||||||
|
<td |
||||||
|
key={index} |
||||||
|
{...cell.getCellProps()} |
||||||
|
className="py-4 px-2 whitespace-nowrap text-sm text-[#00253A] text-right" |
||||||
|
role="cell" |
||||||
|
> |
||||||
|
{cell.render("Cell")} |
||||||
|
</td> |
||||||
|
); |
||||||
|
})} |
||||||
|
</tr> |
||||||
|
{row.isExpanded ? ( |
||||||
|
<tr className="w-full"> |
||||||
|
<td colSpan={headerGroups[0].headers.length}> |
||||||
|
<RowDetail |
||||||
|
data={data} |
||||||
|
schema={schema.filter((e) => e.more)} |
||||||
|
setTabs={setTabs} |
||||||
|
tabs={tabs} |
||||||
|
row={row.original} |
||||||
|
name={name} |
||||||
|
/> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
) : null} |
||||||
|
</React.Fragment> |
||||||
|
); |
||||||
|
})} |
||||||
|
|
||||||
|
{/* {tabs?.length > 1 && editor} */} |
||||||
|
</tbody> |
||||||
|
{/* {tabs?.length > 1 && editor} */} |
||||||
|
<tfoot className={`bg-blue-900 ${rows.length > 5 ? "w-full" : "hidden"}`}> |
||||||
|
{footerGroups.map((footerGroup, index) => ( |
||||||
|
<tr key={index} {...footerGroup.getFooterGroupProps()}> |
||||||
|
{footerGroup.headers.map((column, index) => ( |
||||||
|
<td |
||||||
|
key={index} |
||||||
|
{...column.getFooterProps} |
||||||
|
className="py-2 px-4 text-right text-xs text-white" |
||||||
|
> |
||||||
|
{column.render("Footer")} |
||||||
|
</td> |
||||||
|
))} |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</tfoot> |
||||||
|
</table> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({}); |
||||||
|
|
||||||
|
const mapDispatchToProps = {}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Table); |
@ -0,0 +1,61 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import Table from "./components/Table"; |
||||||
|
|
||||||
|
export const Main = ({ |
||||||
|
getTableProps, |
||||||
|
headerGroups, |
||||||
|
prepareRow, |
||||||
|
filtering, |
||||||
|
setFiltering, |
||||||
|
tabs, |
||||||
|
setTabs, |
||||||
|
getTableBodyProps, |
||||||
|
columns, |
||||||
|
page, |
||||||
|
rows, |
||||||
|
footerGroups, |
||||||
|
name, |
||||||
|
schema, |
||||||
|
data, |
||||||
|
editor, |
||||||
|
activeOtherTab, |
||||||
|
setActiveOtherTab, |
||||||
|
fake, |
||||||
|
setFake |
||||||
|
}) => { |
||||||
|
return ( |
||||||
|
<main className="w-full flex-1"> |
||||||
|
<Table |
||||||
|
getTableProps={getTableProps} |
||||||
|
headerGroups={headerGroups} |
||||||
|
prepareRow={prepareRow} |
||||||
|
filtering={filtering} |
||||||
|
setFiltering={setFiltering} |
||||||
|
tabs={tabs} |
||||||
|
setTabs={setTabs} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
getTableBodyProps={getTableBodyProps} |
||||||
|
columns={columns} |
||||||
|
page={page} |
||||||
|
rows={rows} |
||||||
|
footerGroups={footerGroups} |
||||||
|
name={name} |
||||||
|
schema={schema} |
||||||
|
data={data} |
||||||
|
editor={editor} |
||||||
|
activeOtherTab={activeOtherTab} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
/> |
||||||
|
</main> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({}); |
||||||
|
|
||||||
|
const mapDispatchToProps = {}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Main); |
@ -0,0 +1,21 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
const GoToPage = ({ pageIndex, gotoPage, pageOptions }) => { |
||||||
|
return ( |
||||||
|
<span className="text-xs text-blue-400 mr-1"> |
||||||
|
صفحه{" "} |
||||||
|
<input |
||||||
|
className="ml-1 text-xs p-1 rounded border-blue-400 w-9" |
||||||
|
type="number" |
||||||
|
defaultValue={pageIndex + 1} |
||||||
|
onChange={(e) => { |
||||||
|
const pageNumber = e.target.value ? Number(e.target.value) - 1 : 0; |
||||||
|
gotoPage(pageNumber); |
||||||
|
}} |
||||||
|
/> |
||||||
|
از {pageOptions.length} |
||||||
|
</span> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default GoToPage; |
@ -0,0 +1,61 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
const Pagination = ({ |
||||||
|
gotoPage, |
||||||
|
canPreviousPage, |
||||||
|
previousPage, |
||||||
|
canNextPage, |
||||||
|
pageCount, |
||||||
|
nextPage, |
||||||
|
}) => { |
||||||
|
return ( |
||||||
|
<div className="flex flex-row items-center"> |
||||||
|
<button |
||||||
|
className={`rounded py-1 px-2 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`} |
||||||
|
onClick={() => gotoPage(0)} |
||||||
|
disabled={!canPreviousPage} |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4double-left-arrow.svg" |
||||||
|
className="rotate-180 w-4" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</button> |
||||||
|
<button |
||||||
|
className={`rounded py-1 px-2 ml-1 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`} |
||||||
|
onClick={() => previousPage()} |
||||||
|
disabled={!canPreviousPage} |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4right-arrow.svg" |
||||||
|
className="w-3" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</button> |
||||||
|
<button |
||||||
|
className={`rounded py-1 px-2 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`} |
||||||
|
onClick={() => nextPage()} |
||||||
|
disabled={!canNextPage} |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4left-arrow.svg" |
||||||
|
className="w-3" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</button> |
||||||
|
<button |
||||||
|
className={`rounded py-1 px-2 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`} |
||||||
|
onClick={() => gotoPage(pageCount - 1)} |
||||||
|
disabled={!canNextPage} |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4double-left-arrow.svg" |
||||||
|
className="w-4" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Pagination; |
@ -0,0 +1,60 @@ |
|||||||
|
import React, { useState } from "react"; |
||||||
|
|
||||||
|
const Settings = ({ allColumns, getToggleHideAllColumnsProps }) => { |
||||||
|
const [openModal, setOpenModal] = useState(false); |
||||||
|
return ( |
||||||
|
<section> |
||||||
|
<div |
||||||
|
onClick={() => setOpenModal(true)} |
||||||
|
data-modal-toggle="large-modal" |
||||||
|
className="mr-2 flex flex-col items-center justify-center p-1 bg-[#F9FBFF] rounded" |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4settings.svg" |
||||||
|
className="w-5 cursor-pointer" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
{/* modal */} |
||||||
|
<div |
||||||
|
className={`cursor-pointer w-screen h-screen backdrop-filter backdrop-blur-lg bg-opacity-90 bg-surfaceBorder flex items-center justify-center fixed left-0 top-0
|
||||||
|
${openModal ? "opacity-80" : "opacity-0 pointer-events-none"}`}
|
||||||
|
onClick={() => setOpenModal(false)} |
||||||
|
> |
||||||
|
<div |
||||||
|
className="absolute top-[10%] bg-white w-6/12 p-4 rounded" |
||||||
|
onClick={(e) => e.stopPropagation()} |
||||||
|
> |
||||||
|
<div className=""> |
||||||
|
<h2 className="text-base"> |
||||||
|
با زدن بر روی هر یک از چک باکس ها میتوانید نمایش ستون هارو خاموش |
||||||
|
روشن کنید{" "} |
||||||
|
</h2> |
||||||
|
<div className="text-sm mt-5"> |
||||||
|
<input |
||||||
|
className="ml-2" |
||||||
|
type="checkbox" |
||||||
|
{...getToggleHideAllColumnsProps()} |
||||||
|
/>{" "} |
||||||
|
تاگل کردن همه |
||||||
|
</div> |
||||||
|
{allColumns.map((column, index) => ( |
||||||
|
<div key={index}> |
||||||
|
<label className="text-sm"> |
||||||
|
<input |
||||||
|
type="checkbox" |
||||||
|
className="ml-2 " |
||||||
|
{...column.getToggleHiddenProps()} |
||||||
|
/> |
||||||
|
{column.Footer} |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Settings; |
@ -0,0 +1,128 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
const Tabbar = ({ |
||||||
|
setTabs, |
||||||
|
tabs, |
||||||
|
name, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
activeOtherTab, |
||||||
|
setActiveOtherTab, |
||||||
|
}) => { |
||||||
|
const tabsActionsHandler = React.useCallback( |
||||||
|
(type, id) => { |
||||||
|
if (type === "activation") { |
||||||
|
setTabs({ |
||||||
|
table: name, |
||||||
|
tabs: tabs.map((tab) => { |
||||||
|
return { ...tab, isActive: tab.id === id ? true : false }; |
||||||
|
}), |
||||||
|
}); |
||||||
|
} |
||||||
|
if (type === "delete") { |
||||||
|
setTabs({ table: name, tabs: tabs.filter((tab) => tab.id !== id) }); |
||||||
|
} |
||||||
|
}, |
||||||
|
[tabs] |
||||||
|
); |
||||||
|
const deleteTab = (e, index) => { |
||||||
|
e.stopPropagation(); |
||||||
|
let newList = []; |
||||||
|
for (let i = 0; i < tabs.length; i++) { |
||||||
|
if (i !== index) { |
||||||
|
newList.push(tabs[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
if (tabs[index].isActive) { |
||||||
|
newList[0].isActive = true; |
||||||
|
setActiveOtherTab(false); |
||||||
|
} |
||||||
|
setFake(Math.random() * 1000); |
||||||
|
setTabs(newList); |
||||||
|
}; |
||||||
|
const selectTab = (e, index) => { |
||||||
|
let newList = [...tabs]; |
||||||
|
if (index === 0) { |
||||||
|
newList[0].isActive = true; |
||||||
|
for (let i = 1; i < newList.length; i++) { |
||||||
|
newList[i].isActive = false; |
||||||
|
} |
||||||
|
setActiveOtherTab(false); |
||||||
|
} else { |
||||||
|
for (let i = 0; i < newList.length; i++) { |
||||||
|
if (i === index) { |
||||||
|
newList[i].isActive = true; |
||||||
|
} else { |
||||||
|
newList[i].isActive = false; |
||||||
|
} |
||||||
|
} |
||||||
|
setActiveOtherTab(true); |
||||||
|
} |
||||||
|
setFake(Math.random() * 1000); |
||||||
|
setTabs(newList); |
||||||
|
}; |
||||||
|
// console.log(fake);
|
||||||
|
// console.log("tabs:");
|
||||||
|
// console.log(tabs);
|
||||||
|
return ( |
||||||
|
<ul |
||||||
|
className="tabbar flex no-scrollbar overflow-x-auto scrolling-touch" |
||||||
|
onMouseDown={(e) => { |
||||||
|
const slider = window.document.querySelector(".tabbar"); |
||||||
|
window.isDown = true; |
||||||
|
window.startX = e.pageX - slider.offsetLeft; |
||||||
|
window.scrollLeft = slider.scrollLeft; |
||||||
|
}} |
||||||
|
onMouseLeave={(e) => { |
||||||
|
window.isDown = false; |
||||||
|
}} |
||||||
|
onMouseUp={(e) => { |
||||||
|
window.isDown = false; |
||||||
|
}} |
||||||
|
onMouseMove={(e) => { |
||||||
|
const slider = window.document.querySelector(".tabbar"); |
||||||
|
if (!window.isDown) return; |
||||||
|
e.preventDefault(); |
||||||
|
const x = e.pageX - slider.offsetLeft; |
||||||
|
const walk = (x - window.startX) * 1; |
||||||
|
slider.scrollLeft = window.scrollLeft - walk; |
||||||
|
}} |
||||||
|
> |
||||||
|
{tabs?.map((el, index) => ( |
||||||
|
<li |
||||||
|
key={index} |
||||||
|
onClick={(e) => { |
||||||
|
selectTab(e, index); |
||||||
|
}} |
||||||
|
aria-current="page" |
||||||
|
className={`flex flex-row items-center text-sm text-white px-2 py-0.5 text-center active rounded-sm ${ |
||||||
|
el?.isActive ? "text-[#fff]" : "text-[#000]" |
||||||
|
} ${el?.isActive ? "bg-[#92c1fc]" : "bg-[#F1F6FF]"}`}
|
||||||
|
> |
||||||
|
<div |
||||||
|
className="h-full py-2 pointer-events-none" |
||||||
|
onClick={(e) => tabsActionsHandler("activation", el.id)} |
||||||
|
> |
||||||
|
{el.name} |
||||||
|
</div> |
||||||
|
{el.id ? ( |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable5zarbedar-icon.svg" |
||||||
|
className="w-2 mr-3 cursor-pointer" |
||||||
|
alt="" |
||||||
|
onClick={(e) => { |
||||||
|
deleteTab(e, index); |
||||||
|
}} |
||||||
|
/> |
||||||
|
) : null} |
||||||
|
</li> |
||||||
|
))} |
||||||
|
</ul> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
window.isDown = false; |
||||||
|
|
||||||
|
export default Tabbar; |
||||||
|
|
||||||
|
// if (window.isDown) return e.preventDefault();
|
@ -0,0 +1,21 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import Search from "../../../../Search"; |
||||||
|
|
||||||
|
const TableSearch = ({ globalFilter, setGlobalFilter }) => { |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex flex-row-reverse items-center relative ml-2 "> |
||||||
|
<Search |
||||||
|
icon={false} |
||||||
|
placeholder="جستجو ..." |
||||||
|
padding="py-1" |
||||||
|
value={globalFilter || ' '} |
||||||
|
onChange={(value) => setGlobalFilter(value)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default TableSearch; |
@ -0,0 +1,69 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import Tabbar from "./components/Tabbar"; |
||||||
|
import TableSearch from "./components/TableSearch"; |
||||||
|
import GoToPage from "./components/GoToPage"; |
||||||
|
import Pagination from "./components/Pagination"; |
||||||
|
import Settings from "./components/Settings"; |
||||||
|
|
||||||
|
export const Toolbar = ({ |
||||||
|
setTabs, |
||||||
|
tabs, |
||||||
|
globalFilter, |
||||||
|
setGlobalFilter, |
||||||
|
pageIndex, |
||||||
|
pageOptions, |
||||||
|
gotoPage, |
||||||
|
canPreviousPage, |
||||||
|
previousPage, |
||||||
|
canNextPage, |
||||||
|
pageCount, |
||||||
|
nextPage, |
||||||
|
allColumns, |
||||||
|
getToggleHideAllColumnsProps, |
||||||
|
name, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
activeOtherTab, |
||||||
|
setActiveOtherTab, |
||||||
|
}) => { |
||||||
|
return ( |
||||||
|
<div className="flex justify-between items-center"> |
||||||
|
<Tabbar |
||||||
|
setTabs={setTabs} |
||||||
|
tabs={tabs} |
||||||
|
name={name} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
activeOtherTab={activeOtherTab} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
/> |
||||||
|
<div className="flex py-2 bg-surface items-center gap-2 px-2 bg-sky-200"> |
||||||
|
<TableSearch |
||||||
|
globalFilter={globalFilter} |
||||||
|
setGlobalFilter={setGlobalFilter} |
||||||
|
/> |
||||||
|
<GoToPage |
||||||
|
pageIndex={pageIndex} |
||||||
|
pageOptions={pageOptions} |
||||||
|
gotoPage={gotoPage} |
||||||
|
/> |
||||||
|
<Pagination |
||||||
|
gotoPage={gotoPage} |
||||||
|
canPreviousPage={canPreviousPage} |
||||||
|
previousPage={previousPage} |
||||||
|
canNextPage={canNextPage} |
||||||
|
pageCount={pageCount} |
||||||
|
nextPage={nextPage} |
||||||
|
/> |
||||||
|
<Settings |
||||||
|
allColumns={allColumns} |
||||||
|
getToggleHideAllColumnsProps={getToggleHideAllColumnsProps} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Toolbar; |
@ -0,0 +1,14 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
const Checkbox = React.forwardRef(({ indeterminate, ...rest }, ref) => { |
||||||
|
const defaultRef = React.useRef(); |
||||||
|
const resolvedRef = ref || defaultRef; |
||||||
|
|
||||||
|
React.useEffect(() => { |
||||||
|
resolvedRef.current.indeterminate = indeterminate; |
||||||
|
}, [resolvedRef, indeterminate]); |
||||||
|
|
||||||
|
return <input type="checkbox" ref={resolvedRef} {...rest} />; |
||||||
|
}); |
||||||
|
|
||||||
|
export default Checkbox; |
@ -0,0 +1,15 @@ |
|||||||
|
import React, { useState } from "react"; |
||||||
|
|
||||||
|
export const ColumnSearch = ({ column }) => { |
||||||
|
const { filterValue, setFilter } = column; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="w-full flex flex-row items-center "> |
||||||
|
<input |
||||||
|
value={filterValue || ""} |
||||||
|
onChange={(e) => setFilter(e.target.value)} |
||||||
|
className={`w-full h-8 text-xs text-blue-400 bg-[#F9FBFF] border-[#B5D6FF] rounded focus:outline-none border p-1 focus:border-blue-200 focus:ring focus:ring-blue-200 focus:ring-opacity-20`} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,98 @@ |
|||||||
|
import { tab } from "@testing-library/user-event/dist/tab"; |
||||||
|
import { useState } from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
import { table } from "../../../../../redux/actions"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import DropDown from "./Dropdown"; |
||||||
|
|
||||||
|
const Buttons = ({ |
||||||
|
row, |
||||||
|
setTabs, |
||||||
|
tabs, |
||||||
|
name, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
setActiveOtherTab, |
||||||
|
activeOtherTab, |
||||||
|
}) => { |
||||||
|
const [activeDetails, setActiveDetails] = useState(null); |
||||||
|
const editHandler = () => { |
||||||
|
console.log("edit click"); |
||||||
|
console.log(fake); |
||||||
|
console.log(tabs); |
||||||
|
// console.log(row);
|
||||||
|
// console.log(name);
|
||||||
|
let newList = [...tabs]; |
||||||
|
console.log(newList); |
||||||
|
for (let i = 0; i < newList.length; i++) { |
||||||
|
newList[i].isActive = false; |
||||||
|
} |
||||||
|
newList.push({ |
||||||
|
id: Math.floor(Math.random() * 10000), |
||||||
|
name: row.original.name, |
||||||
|
isActive: true, |
||||||
|
data: row, |
||||||
|
element: "editor", |
||||||
|
}); |
||||||
|
console.log(newList); |
||||||
|
console.log("!!!!!!!!!!!!!!!!!!!!!!!"); |
||||||
|
setFake(Math.random() * 10000); |
||||||
|
setTabs(newList); |
||||||
|
setActiveOtherTab(true); |
||||||
|
}; |
||||||
|
return ( |
||||||
|
<div className="flex flex-row items-center justify-end "> |
||||||
|
<div |
||||||
|
className="" |
||||||
|
onClick={() => { |
||||||
|
setActiveDetails(!activeDetails); |
||||||
|
}} |
||||||
|
> |
||||||
|
<button |
||||||
|
className="mx-1 flex flex-row items-center justify-around p-2 rounded border border-gray-300 text-[12px]" |
||||||
|
{...row.getToggleRowExpandedProps()} |
||||||
|
> |
||||||
|
جزئیات |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4arrow-icon.svg" |
||||||
|
alt="" |
||||||
|
className={`mx-1 w-3 cursor-pointer mr-2 transition-all duration-500 ${ |
||||||
|
activeDetails ? "rotate-60" : "rotate-90" |
||||||
|
}`}
|
||||||
|
/> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div |
||||||
|
className="mx-1 p-1 rounded border border-gray-300 cursor-pointer" |
||||||
|
onClick={editHandler} |
||||||
|
> |
||||||
|
<img src="/icons/table/ReactTable4edit.svg" alt="" className="w-10" /> |
||||||
|
</div> |
||||||
|
{/* <DropDown /> */} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({ |
||||||
|
tabs: state.table.tabs, |
||||||
|
}); |
||||||
|
|
||||||
|
const mapDispatchToProps = { |
||||||
|
setTabs: table.setTab, |
||||||
|
}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Buttons); |
||||||
|
|
||||||
|
// setTabs({
|
||||||
|
// table: name,
|
||||||
|
// tabs: [
|
||||||
|
// ...tabs,
|
||||||
|
// { name: row.original.name, id: row.original.id, isActive: false },
|
||||||
|
// ].map((tab) => {
|
||||||
|
// return {
|
||||||
|
// ...tab,
|
||||||
|
// isActive: row.original.id === tab.id ? true : false,
|
||||||
|
// };
|
||||||
|
// }),
|
||||||
|
// });
|
@ -0,0 +1,53 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
const DropDown = ({}) => { |
||||||
|
return ( |
||||||
|
<div className="mr-3"> |
||||||
|
<button |
||||||
|
className="" |
||||||
|
id="dropdownButton" |
||||||
|
data-dropdown-toggle="dropdown" |
||||||
|
type="button" |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/icons/table/DropDownmore2.svg" |
||||||
|
alt="" |
||||||
|
className={`cursor-pointer w-5`} |
||||||
|
/> |
||||||
|
</button> |
||||||
|
<div |
||||||
|
id="dropdown" |
||||||
|
className="hidden z-10 text-xs list-none bg-[#65A9FF] rounded divide-y divide-gray-100 shadow" |
||||||
|
> |
||||||
|
<ul className="" aria-labelledby="dropdownButton"> |
||||||
|
<li> |
||||||
|
<a |
||||||
|
href="#" |
||||||
|
className="block p-3 text-xs text-white hover:bg-blue-600 hover:rounded" |
||||||
|
> |
||||||
|
ایتم شماره1 |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a |
||||||
|
href="#" |
||||||
|
className="block p-3 text-xs text-white hover:bg-blue-600 hover:rounded" |
||||||
|
> |
||||||
|
ایتم شماره1 |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a |
||||||
|
href="#" |
||||||
|
className="block p-3 text-xs text-white hover:bg-blue-600 hover:rounded" |
||||||
|
> |
||||||
|
ایتم شماره1 |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default DropDown; |
@ -0,0 +1,75 @@ |
|||||||
|
import React, { useState } from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
import { table } from "../../../../../redux/actions"; |
||||||
|
|
||||||
|
//components
|
||||||
|
import DropDown from "./Dropdown"; |
||||||
|
|
||||||
|
const EditButtons = ({ |
||||||
|
setFiltering, |
||||||
|
filtering, |
||||||
|
setTabs, |
||||||
|
tabs, |
||||||
|
name, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
setActiveOtherTab, |
||||||
|
}) => { |
||||||
|
const [selectedFilterRow, setSelectedFilterRow] = useState(filtering); |
||||||
|
|
||||||
|
const addNewItem = () => { |
||||||
|
let newList = [...tabs]; |
||||||
|
newList[0].isActive = false; |
||||||
|
newList.push({ |
||||||
|
name: "ردیف جدید", |
||||||
|
isActive: true, |
||||||
|
data: null, |
||||||
|
id: Math.floor(Math.random() * 10000), |
||||||
|
element: "editor", |
||||||
|
}); |
||||||
|
setFake(Math.random() * 10000); |
||||||
|
setTabs(newList); |
||||||
|
setActiveOtherTab(true); |
||||||
|
}; |
||||||
|
return ( |
||||||
|
<div className="flex flex-row items-center justify-end"> |
||||||
|
{/* add new row */} |
||||||
|
<button |
||||||
|
className="mx-1 flex flex-row items-center bg-[#65A9FF] p-2 text-xs text-white rounded border-b-2 border-blue-600" |
||||||
|
onClick={addNewItem} |
||||||
|
> |
||||||
|
<div>ثبت جدید</div> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4plus.svg" |
||||||
|
alt="" |
||||||
|
className={`cursor-pointer w-2 mr-1 `} |
||||||
|
/> |
||||||
|
</button> |
||||||
|
{/* filter */} |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4filter.svg" |
||||||
|
alt="" |
||||||
|
className={`mx-1 p-2 rounded border border-blue-300 cursor-pointer w-8
|
||||||
|
${selectedFilterRow ? "border" : "border-0"}`}
|
||||||
|
onClick={() => { |
||||||
|
setFiltering(!selectedFilterRow); |
||||||
|
setSelectedFilterRow(!selectedFilterRow); |
||||||
|
}} |
||||||
|
/> |
||||||
|
{/* dropDown */} |
||||||
|
{/* <DropDown /> */} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({ |
||||||
|
tabs: state.table.tabs, |
||||||
|
}); |
||||||
|
|
||||||
|
const mapDispatchToProps = { |
||||||
|
setTabs: table.setTab, |
||||||
|
}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(EditButtons); |
||||||
|
|
||||||
|
EditButtons.defaultProps = {}; |
@ -0,0 +1,34 @@ |
|||||||
|
const status = { |
||||||
|
1: { |
||||||
|
color: "#65FFAD", |
||||||
|
label: "وضعیت عادی", |
||||||
|
}, |
||||||
|
2: { |
||||||
|
color: "#FF6565", |
||||||
|
label: "اعلان سطح هشدار", |
||||||
|
}, |
||||||
|
3: { |
||||||
|
color: "#65DAFF", |
||||||
|
label: "بررسی و پیگیری", |
||||||
|
}, |
||||||
|
4: { |
||||||
|
color: "#FFEA65", |
||||||
|
label: "وضعیت بحرانی", |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export function OrderLevel({ value }) { |
||||||
|
return ( |
||||||
|
<div className="p-2 rounded-3xl border border-[#aaa] w-fit"> |
||||||
|
<div className="flex flex-row items-center justify-center gap-2"> |
||||||
|
<span |
||||||
|
className={`w-2 h-2 rounded-full`} |
||||||
|
style={{
|
||||||
|
backgroundColor: status[value].color |
||||||
|
}} |
||||||
|
></span> |
||||||
|
<span className="text-[#00253A]">{status[value].label}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
const statusMap = { |
||||||
|
1: { |
||||||
|
label: "ارسال شده", |
||||||
|
bg: "#E2FFE377", |
||||||
|
color: "#00613D", |
||||||
|
}, |
||||||
|
2: { |
||||||
|
label: "رسیدگی", |
||||||
|
bg: "#FFF78733", |
||||||
|
color: "#ECF150", |
||||||
|
}, |
||||||
|
3: { |
||||||
|
label: "لغو شده", |
||||||
|
bg: "#FFD8CC33", |
||||||
|
color: "#611100", |
||||||
|
}, |
||||||
|
4: { |
||||||
|
label: "پیگیری", |
||||||
|
bg: "#E2FFFD33", |
||||||
|
color: "#00614A", |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export function OrderStatus({ value }) { |
||||||
|
value = +value; |
||||||
|
return ( |
||||||
|
<div className=""> |
||||||
|
<span |
||||||
|
className={`p-2 rounded bg-[${statusMap[value].bg}] text-[${statusMap[value].color}]`} |
||||||
|
> |
||||||
|
{statusMap[value].label} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
export const SelectFilter = ({ |
||||||
|
column: { filterValue, setFilter, preFilteredRows, id, render }, |
||||||
|
}) => { |
||||||
|
const options = React.useMemo(() => { |
||||||
|
const options = new Set(); |
||||||
|
preFilteredRows.forEach((row) => { |
||||||
|
options.add(row.values[id]); |
||||||
|
}); |
||||||
|
return [...options.values()]; |
||||||
|
}, [id, preFilteredRows]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<select |
||||||
|
className="text-xs bg-[#F9FBFF] border-[#B5D6FF] flex flex-row items-start justify-start p-1 text-blue-400 rounded focus:outline-none border focus:border-blue-200 focus:ring focus:ring-blue-200 focus:ring-opacity-20" |
||||||
|
name={id} |
||||||
|
id={id} |
||||||
|
value={filterValue} |
||||||
|
onChange={(e) => { |
||||||
|
setFilter(e.target.value || undefined); |
||||||
|
}} |
||||||
|
> |
||||||
|
<option className="text-white bg-[#65A9FF]" value=""> |
||||||
|
همه |
||||||
|
</option> |
||||||
|
{options.map((option, i) => ( |
||||||
|
<option |
||||||
|
key={i} |
||||||
|
value={option} |
||||||
|
className="text-right text-white px-2 bg-[#65A9FF]" |
||||||
|
> |
||||||
|
{(() => { |
||||||
|
if (option === 1) { |
||||||
|
return "ارسال شده"; |
||||||
|
} else if (option === 2) { |
||||||
|
return "رسیدگی"; |
||||||
|
} else if (option === 3) { |
||||||
|
return "لغو شده"; |
||||||
|
} else if (option === 4) { |
||||||
|
return "پیگیری"; |
||||||
|
} else { |
||||||
|
return "خطری"; |
||||||
|
} |
||||||
|
})()}{" "} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default SelectFilter; |
@ -0,0 +1,39 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
export const SliderColumnFilter = ({ |
||||||
|
column: { filterValue, setFilter, preFilteredRows, id }, |
||||||
|
}) => { |
||||||
|
const [min, max] = React.useMemo(() => { |
||||||
|
let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0; |
||||||
|
let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0; |
||||||
|
preFilteredRows.forEach((row) => { |
||||||
|
min = Math.min(row.values[id], min); |
||||||
|
max = Math.max(row.values[id], max); |
||||||
|
}); |
||||||
|
return [min, max]; |
||||||
|
}, [id, preFilteredRows]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex flex-row items-center justify-between w-10/12"> |
||||||
|
<input |
||||||
|
className=" w-10/12" |
||||||
|
type="range" |
||||||
|
min={min}
|
||||||
|
max={max} |
||||||
|
value={filterValue || min} |
||||||
|
onChange={(e) => { |
||||||
|
setFilter(parseInt(e.target.value, 10)); |
||||||
|
}} |
||||||
|
/> |
||||||
|
<div className="p-1 rounded bg-blue-300 bg-opacity-20"> |
||||||
|
<img |
||||||
|
src="/icons/table/ReactTable4zarbedar-icon.svg" |
||||||
|
onClick={() => setFilter(undefined)} |
||||||
|
className="cursor-pointer w-2 h-2" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
{/* <button onClick={() => setFilter(undefined)}>ریست</button> */} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,95 @@ |
|||||||
|
// import { format } from "date-fns";
|
||||||
|
|
||||||
|
//components
|
||||||
|
import { OrderStatus } from "./components/OrderStatus"; |
||||||
|
import { OrderLevel } from "./components/OrderLevel"; |
||||||
|
import Buttons from "./components/Buttons"; |
||||||
|
import EditButtons from "./components/EditButtons"; |
||||||
|
import SelectFilter from "./components/SelectFilter.js"; |
||||||
|
import { SliderColumnFilter } from "./components/SliderColumnFilter.js"; |
||||||
|
import moment from "jalali-moment"; |
||||||
|
import { ColumnSearch } from "../ColumnSearch"; |
||||||
|
|
||||||
|
const Components = { |
||||||
|
date: ({ value }) => { |
||||||
|
// return format(new Date(value), "dd/MM/yyyy");
|
||||||
|
// return moment(value).locale("fa").format("YYYY-MM-DD")
|
||||||
|
return moment(value, "YYYY/MM/DD") |
||||||
|
.locale("fa") |
||||||
|
.format("YYYY/MM/DD") |
||||||
|
}, |
||||||
|
OrderStatus, |
||||||
|
OrderLevel, |
||||||
|
text: ({ value }) => value, |
||||||
|
string: ({ value }) => value, |
||||||
|
number: ({ value }) => value, |
||||||
|
}; |
||||||
|
|
||||||
|
const Filters = { |
||||||
|
SliderColumnFilter: SliderColumnFilter, |
||||||
|
SelectFilter: SelectFilter, |
||||||
|
ColumnSrarch:ColumnSearch |
||||||
|
}; |
||||||
|
|
||||||
|
export const COLUMNS = ({ |
||||||
|
schema, |
||||||
|
setFiltering, |
||||||
|
filtering, |
||||||
|
setTabs, |
||||||
|
name, |
||||||
|
tabs, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
setActiveOtherTab, |
||||||
|
activeOtherTab, |
||||||
|
}) => [ |
||||||
|
...schema.map((e) => ({ |
||||||
|
accessor: e.name, |
||||||
|
Header: e.description, |
||||||
|
// Cell: e.renderer ? Components[e.renderer] : Components["text"],
|
||||||
|
Cell: e.type ? Components[e.type] : Components["text"], |
||||||
|
Filter: e.filter ? Filters[e.filter] : Filters["ColumnSrarch"], |
||||||
|
disableSortBy: e.sort == false, |
||||||
|
Footer: "ستون " + e.description, |
||||||
|
})), |
||||||
|
{ |
||||||
|
Header: () => ( |
||||||
|
<EditButtons |
||||||
|
setFiltering={setFiltering} |
||||||
|
filtering={filtering} |
||||||
|
name={name} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
/> |
||||||
|
), |
||||||
|
Footer: "ستون ", |
||||||
|
accessor: "edit", |
||||||
|
Cell: ({ row }) => ( |
||||||
|
<Buttons |
||||||
|
row={row} |
||||||
|
name={name} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
activeOtherTab={activeOtherTab} |
||||||
|
/> |
||||||
|
), |
||||||
|
disableSortBy: true, |
||||||
|
disableFilters: true, |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Cell: ({ row }) =>
|
||||||
|
// Buttons({
|
||||||
|
// row,
|
||||||
|
// setTabs,
|
||||||
|
// tabs,
|
||||||
|
// name,
|
||||||
|
// fake,
|
||||||
|
// setFake,
|
||||||
|
// setActiveOtherTab,
|
||||||
|
// activeOtherTab,
|
||||||
|
// }),
|
@ -0,0 +1,57 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { OrderLevel } from "./Columns/components/OrderLevel"; |
||||||
|
import { OrderStatus } from "./Columns/components/OrderStatus"; |
||||||
|
|
||||||
|
const components = { |
||||||
|
orderLevel: (val) => <OrderLevel value={val} />, |
||||||
|
orderStatus: (val) => <OrderStatus value={val} />, |
||||||
|
string: (val) => <span className="text-black">{val}</span>, |
||||||
|
number: (val) => <span className="text-black">{val}</span>, |
||||||
|
default: (val) => <span className="text-black">{val}</span>, |
||||||
|
}; |
||||||
|
|
||||||
|
const RowDetail = ({ schema, data, tabs, setTabs, row, name,fake,setFake }) => { |
||||||
|
return ( |
||||||
|
<div className="w-full flex items-center border-r-4 border-blue-600 p-2 bg-[#FBFCFE]"> |
||||||
|
{/* right */} |
||||||
|
<div className="w-[80%] flex flex-wrap items-center "> |
||||||
|
{schema.map((field) => ( |
||||||
|
<div className="flex items-center mr-2 my-2"> |
||||||
|
<div className="w-1 h-4 ml-1 rounded bg-[#EDF2FB]"></div> |
||||||
|
<p className="flex items-center gap-1 text-xs text-blue-400 border-[#65A9FF]"> |
||||||
|
{field?.description} |
||||||
|
{components[field?.type || "default"](data[0][field?.name])} |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
{/* left */} |
||||||
|
<div className="w-[20%] flex flex-col items-end justify-end"> |
||||||
|
<button className="border border-[#D53921] text-[#D53921] rounded w-8/12 text-xs py-2 mb-2 bg-white"> |
||||||
|
حذف |
||||||
|
</button> |
||||||
|
<button |
||||||
|
className="border border-[#65A9FF] text-[#65A9FF] rounded w-8/12 text-xs py-2 bg-white" |
||||||
|
onClick={() => { |
||||||
|
setTabs({ |
||||||
|
table: name, |
||||||
|
tabs: [ |
||||||
|
...tabs, |
||||||
|
{ name: row.name, id: row.id, isActive: true }, |
||||||
|
].map((tab) => { |
||||||
|
return { |
||||||
|
...tab, |
||||||
|
isActive: row.id === tab.id ? true : false, |
||||||
|
}; |
||||||
|
}), |
||||||
|
}); |
||||||
|
}} |
||||||
|
> |
||||||
|
نمایش |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default RowDetail; |
@ -0,0 +1,213 @@ |
|||||||
|
import React, { useState, useMemo } from "react"; |
||||||
|
import { |
||||||
|
useTable, |
||||||
|
useSortBy, |
||||||
|
useGlobalFilter, |
||||||
|
useFilters, |
||||||
|
usePagination, |
||||||
|
useRowSelect, |
||||||
|
useColumnOrder, |
||||||
|
useExpanded, |
||||||
|
} from "react-table"; |
||||||
|
|
||||||
|
//components
|
||||||
|
// import Breadrumbs from "../Breadcrumbs";
|
||||||
|
import Toolbar from "./Layouts/Toolbar"; |
||||||
|
import Checkbox from "./components/Checkbox"; |
||||||
|
import { COLUMNS } from "./components/Columns"; |
||||||
|
import { ColumnSearch } from "../Table/components/ColumnSearch"; |
||||||
|
|
||||||
|
import Footer from "./Layouts/Footer"; |
||||||
|
import Main from "./Layouts/Main"; |
||||||
|
|
||||||
|
const Table = ({ |
||||||
|
data, |
||||||
|
schema, |
||||||
|
name, |
||||||
|
Editor, |
||||||
|
getData, |
||||||
|
title, |
||||||
|
tabs, |
||||||
|
setTabs, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
activeOtherTab, |
||||||
|
setActiveOtherTab, |
||||||
|
add, |
||||||
|
update, |
||||||
|
}) => { |
||||||
|
const [filtering, setFiltering] = useState(false); |
||||||
|
|
||||||
|
const columns = useMemo( |
||||||
|
() => |
||||||
|
COLUMNS({ |
||||||
|
schema: schema.filter((e) => !e.more), |
||||||
|
filtering, |
||||||
|
setFiltering, |
||||||
|
setTabs, |
||||||
|
name, |
||||||
|
tabs, |
||||||
|
fake, |
||||||
|
setFake, |
||||||
|
activeOtherTab, |
||||||
|
setActiveOtherTab, |
||||||
|
}), |
||||||
|
[filtering, schema] |
||||||
|
); |
||||||
|
|
||||||
|
const EditableCell = ({ |
||||||
|
value: initialValue, |
||||||
|
row: { index }, |
||||||
|
column: { id }, |
||||||
|
updateMyData, // This is a custom function that we supplied to our table instance
|
||||||
|
}) => { |
||||||
|
// We need to keep and update the state of the cell normally
|
||||||
|
const [value, setValue] = React.useState(initialValue); |
||||||
|
|
||||||
|
const onChange = (e) => { |
||||||
|
setValue(e.target.value); |
||||||
|
}; |
||||||
|
|
||||||
|
// We'll only update the external data when the input is blurred
|
||||||
|
const onBlur = () => { |
||||||
|
updateMyData(index, id, value); |
||||||
|
}; |
||||||
|
|
||||||
|
// If the initialValue is changed external, sync it up with our state
|
||||||
|
React.useEffect(() => { |
||||||
|
setValue(initialValue); |
||||||
|
}, [initialValue]); |
||||||
|
|
||||||
|
return <input value={value} onChange={onChange} onBlur={onBlur} />; |
||||||
|
}; |
||||||
|
|
||||||
|
const defaultColumn = React.useMemo(() => { |
||||||
|
return { |
||||||
|
Filter: ColumnSearch, |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
|
||||||
|
const { |
||||||
|
getTableProps, |
||||||
|
getTableBodyProps, |
||||||
|
headerGroups, |
||||||
|
footerGroups, |
||||||
|
rows, |
||||||
|
page, |
||||||
|
nextPage, |
||||||
|
previousPage, |
||||||
|
canNextPage, |
||||||
|
canPreviousPage, |
||||||
|
pageOptions, |
||||||
|
gotoPage, |
||||||
|
pageCount, |
||||||
|
setPageSize, |
||||||
|
prepareRow, |
||||||
|
allColumns, |
||||||
|
getToggleHideAllColumnsProps, |
||||||
|
setColumnOrder, |
||||||
|
selectedFlatRows, |
||||||
|
state, |
||||||
|
setGlobalFilter, |
||||||
|
resetResizing, |
||||||
|
} = useTable( |
||||||
|
{ |
||||||
|
columns, |
||||||
|
data, |
||||||
|
defaultColumn, |
||||||
|
}, |
||||||
|
useColumnOrder, |
||||||
|
useFilters, |
||||||
|
useGlobalFilter, |
||||||
|
useSortBy, |
||||||
|
useExpanded, |
||||||
|
usePagination, |
||||||
|
useRowSelect, |
||||||
|
(hooks) => { |
||||||
|
hooks.visibleColumns.push((columns) => { |
||||||
|
return [ |
||||||
|
{ |
||||||
|
id: "selection", |
||||||
|
Header: ({ getToggleAllRowsSelectedProps }) => ( |
||||||
|
<Checkbox {...getToggleAllRowsSelectedProps()} /> |
||||||
|
), |
||||||
|
Cell: ({ row }) => ( |
||||||
|
<Checkbox {...row.getToggleRowSelectedProps()} /> |
||||||
|
), |
||||||
|
}, |
||||||
|
...columns, |
||||||
|
]; |
||||||
|
}); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
const { globalFilter, pageIndex, pageSize } = state; |
||||||
|
const acitvieTab = tabs?.find((e) => e.isActive) || {}; |
||||||
|
return ( |
||||||
|
<div className="w-full flex flex-col flex-1"> |
||||||
|
<Toolbar |
||||||
|
setTabs={setTabs} |
||||||
|
tabs={tabs} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
activeOtherTab={activeOtherTab} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
setGlobalFilter={setGlobalFilter} |
||||||
|
globalFilter={globalFilter} |
||||||
|
pageIndex={pageIndex} |
||||||
|
pageOptions={pageOptions} |
||||||
|
gotoPage={gotoPage} |
||||||
|
canPreviousPage={canPreviousPage} |
||||||
|
previousPage={previousPage} |
||||||
|
canNextPage={canNextPage} |
||||||
|
pageCount={pageCount} |
||||||
|
nextPage={nextPage} |
||||||
|
allColumns={allColumns} |
||||||
|
getToggleHideAllColumnsProps={getToggleHideAllColumnsProps} |
||||||
|
name={name} |
||||||
|
/> |
||||||
|
{acitvieTab.element == "table" && ( |
||||||
|
<Main |
||||||
|
getTableProps={getTableProps} |
||||||
|
headerGroups={headerGroups} |
||||||
|
prepareRow={prepareRow} |
||||||
|
filtering={filtering} |
||||||
|
setTabs={setTabs} |
||||||
|
tabs={tabs} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
activeOtherTab={activeOtherTab} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
columns={columns} |
||||||
|
getTableBodyProps={getTableBodyProps} |
||||||
|
page={page} |
||||||
|
rows={rows} |
||||||
|
footerGroups={footerGroups} |
||||||
|
name={name} |
||||||
|
schema={schema} |
||||||
|
data={data} |
||||||
|
/> |
||||||
|
)} |
||||||
|
{acitvieTab.element == "table" && ( |
||||||
|
<Footer |
||||||
|
pageIndex={pageIndex} |
||||||
|
gotoPage={gotoPage} |
||||||
|
pageOptions={pageOptions} |
||||||
|
pageSize={pageSize} |
||||||
|
setPageSize={setPageSize} |
||||||
|
/> |
||||||
|
)} |
||||||
|
{acitvieTab.element == "editor" && ( |
||||||
|
<Editor |
||||||
|
schema={schema} |
||||||
|
data={acitvieTab.data} |
||||||
|
add={add} |
||||||
|
update={update} |
||||||
|
/> |
||||||
|
)} |
||||||
|
{/* {acitvieTab.element == "viewer" && <Viewer />} */} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Table; |
@ -0,0 +1,8 @@ |
|||||||
|
const table = { |
||||||
|
setTab: |
||||||
|
(data = {}) => |
||||||
|
async (dispatch) => |
||||||
|
await dispatch({ type: "table/setTab", data }), |
||||||
|
}; |
||||||
|
|
||||||
|
export default table; |
@ -0,0 +1,126 @@ |
|||||||
|
const initialState = { |
||||||
|
productTable: { |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
name: "خسرو سهرابی", |
||||||
|
order_date: "1400/11/09", |
||||||
|
order_price: 1, |
||||||
|
order_number: "09120023100", |
||||||
|
user_job: "مهندس ساختمان", |
||||||
|
order_status: 1, |
||||||
|
order_level: 1, |
||||||
|
birth_place: "استان تهران شهر تهران", |
||||||
|
office_phone: "02144407010", |
||||||
|
education: "کارشناسی", |
||||||
|
job: "فروشنده لوازم خانگی", |
||||||
|
field_study: "مدیریت صنعتی", |
||||||
|
birth_day: "1368/09/02", |
||||||
|
uni: "تهران", |
||||||
|
address: |
||||||
|
"خیابان شریعتی نرسیده به خیابان دولت کوچه عباسی نبش تهیه غذای فارسی پلاک 4 واحد 3", |
||||||
|
behaviour: |
||||||
|
"بچه پرو و دریده شاخ اما کم آزار اهل رفاقت و مرام خلاق نوآور آشنا به صنایع خلاق", |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
name: "خسرو سهرابی", |
||||||
|
order_date: "1400/11/09", |
||||||
|
order_price: 1, |
||||||
|
order_number: "09120023100", |
||||||
|
user_job: "مهندس ساختمان", |
||||||
|
order_status: 1, |
||||||
|
order_level: 1, |
||||||
|
birth_place: "استان تهران شهر تهران", |
||||||
|
office_phone: "02144407010", |
||||||
|
education: "کارشناسی", |
||||||
|
job: "فروشنده لوازم خانگی", |
||||||
|
field_study: "مدیریت صنعتی", |
||||||
|
birth_day: "1368/09/02", |
||||||
|
uni: "تهران", |
||||||
|
address: |
||||||
|
"خیابان شریعتی نرسیده به خیابان دولت کوچه عباسی نبش تهیه غذای فارسی پلاک 4 واحد 3", |
||||||
|
behaviour: |
||||||
|
"بچه پرو و دریده شاخ اما کم آزار اهل رفاقت و مرام خلاق نوآور آشنا به صنایع خلاق", |
||||||
|
}, |
||||||
|
], |
||||||
|
schema: [ |
||||||
|
{ |
||||||
|
description: "نام و نام خانوادگی", |
||||||
|
name: "name", |
||||||
|
type: "text", |
||||||
|
sort: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "تاریخ سفارش", |
||||||
|
name: "order_date", |
||||||
|
filter: "SliderColumnFilter", |
||||||
|
type: "text", |
||||||
|
renderer: "date", |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "مبلغ سفارش", |
||||||
|
name: "order_price", |
||||||
|
type: "text", |
||||||
|
filter: "SliderColumnFilter", |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "شماره سفارش", |
||||||
|
name: "order_number", |
||||||
|
type: "text", |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "شغل کاربر", |
||||||
|
name: "user_job", |
||||||
|
type: "text", |
||||||
|
more: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "وضعیت سفارش", |
||||||
|
type: "text", |
||||||
|
name: "order_status", |
||||||
|
renderer: "OrderStatus", |
||||||
|
filter: "SelectFilter", |
||||||
|
more: true, |
||||||
|
enum: { |
||||||
|
1: "ارسال شده", |
||||||
|
2: "رسیدگی", |
||||||
|
3: "لغو شده", |
||||||
|
4: "پیگیری", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "سطح اهمیت", |
||||||
|
type: "text", |
||||||
|
name: "order_level", |
||||||
|
renderer: "OrderLevel", |
||||||
|
filter: "SelectFilter", |
||||||
|
more: true, |
||||||
|
enum: { |
||||||
|
1: "وضعیت عادی", |
||||||
|
2: "اعلان سطح هشدار", |
||||||
|
3: "بررسی و پیگیری", |
||||||
|
4: "وضعیت بحرانی", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
tabs: [], |
||||||
|
// table2: {
|
||||||
|
// tabs: [],
|
||||||
|
// },
|
||||||
|
}; |
||||||
|
|
||||||
|
export default function table(state = initialState, action) { |
||||||
|
let { type, data } = action; |
||||||
|
switch (type) { |
||||||
|
case "table/setTab": |
||||||
|
console.log("reducer:") |
||||||
|
console.log(data); |
||||||
|
return { |
||||||
|
...state,tabs:data |
||||||
|
}; |
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
|
||||||
|
|
||||||
|
import React, { useMemo, useState } from "react"; |
||||||
|
import { useEffect } from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
import Table from "../../components/Table"; |
||||||
|
import * as actions from "../../redux/actions"; |
||||||
|
import Editor from "./layouts/Editor"; |
||||||
|
|
||||||
|
const source = "user"; |
||||||
|
const title = " مدیریت کاربران"; |
||||||
|
|
||||||
|
const UserManagement = ({ |
||||||
|
info, |
||||||
|
getData, |
||||||
|
tabs, |
||||||
|
setTabs, |
||||||
|
add, |
||||||
|
update, |
||||||
|
getSchoolList, |
||||||
|
schoolList |
||||||
|
}) => { |
||||||
|
useMemo(() => { |
||||||
|
// getData({ num: 1000 });
|
||||||
|
getSchoolList({ productType :7}); |
||||||
|
}, []); |
||||||
|
// console.log(info);
|
||||||
|
console.log(schoolList); |
||||||
|
useEffect(() => { |
||||||
|
setTabs([ |
||||||
|
{ |
||||||
|
id: 0, |
||||||
|
name: title, |
||||||
|
isActive: true, |
||||||
|
element: "table", |
||||||
|
}, |
||||||
|
]); |
||||||
|
}, []); |
||||||
|
console.log("ProductManagement render---------------------------------"); |
||||||
|
// console.log(tabs);
|
||||||
|
// const [tabs, setTabs] = useState([
|
||||||
|
// {
|
||||||
|
// id: 0,
|
||||||
|
// name: title,
|
||||||
|
// isActive: true,
|
||||||
|
// },
|
||||||
|
// ]);
|
||||||
|
const [activeOtherTab, setActiveOtherTab] = useState(false); |
||||||
|
const [fake, setFake] = useState(0); |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className="UserManagement w-[95.5%] h-screen max-h-screen bg-[#F8F8F8] overflow-y-auto rtl relative" |
||||||
|
// style={{ width: "calc(100% - 4.5%)" }}
|
||||||
|
> |
||||||
|
<div className="w-full h-full absolute top-0 left-0 bg-[#ffffff10] backdrop-blur z-[100] flex items-center justify-center"> |
||||||
|
<div className="w-1/2 py-4 px-2 rounded bg-white flex items-center justify-between"> |
||||||
|
<select |
||||||
|
name="UserManagement-school-select" |
||||||
|
id="UserManagement-school-select" |
||||||
|
className="w-[70%] h-12 rounded px-1 py-0" |
||||||
|
> |
||||||
|
{schoolList?.map((item,index)=>( |
||||||
|
<option key={index} value={item.id}>{item?.name}</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
|
<div className="h-12 w-[25%] rounded flex items-center justify-center text-white bg-red52"> |
||||||
|
تایید |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="flex flex-1 w-full pt-12 pb-4"> |
||||||
|
<Table |
||||||
|
data={info.list || []} |
||||||
|
schema={info.schema || []} |
||||||
|
Editor={Editor} |
||||||
|
add={add} |
||||||
|
update={update} |
||||||
|
getData={getData} |
||||||
|
title={title} |
||||||
|
tabs={tabs} |
||||||
|
setTabs={setTabs} |
||||||
|
fake={fake} |
||||||
|
setFake={setFake} |
||||||
|
activeOtherTab={activeOtherTab} |
||||||
|
setActiveOtherTab={setActiveOtherTab} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
const mapStateToProps = (state) => ({ |
||||||
|
info: state[source], |
||||||
|
tabs: state.table.tabs, |
||||||
|
schoolList: state.book.list |
||||||
|
}); |
||||||
|
|
||||||
|
const mapDispatchToProps = { |
||||||
|
getData: actions[source].list, |
||||||
|
setTabs: actions.table.setTab, |
||||||
|
update: actions[source].update, |
||||||
|
add: actions[source].add, |
||||||
|
getSchoolList : actions.book.list |
||||||
|
}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(UserManagement); |
@ -0,0 +1,202 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
import { file } from "../../../redux/actions"; |
||||||
|
import loadingGiff from "../../../assets/img/Rolling-1s-200px.gif"; |
||||||
|
import SimpleDatePicker from "../../../components/SimpleDatePicker"; |
||||||
|
import moment from "jalali-moment"; |
||||||
|
const baseUrl = "https://light.barnamenegar.com/api/v1"; |
||||||
|
const components = (props) => ({ |
||||||
|
text: ( |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]" |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
), |
||||||
|
string: ( |
||||||
|
<textarea |
||||||
|
type="text" |
||||||
|
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]" |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
), |
||||||
|
number: ( |
||||||
|
<input |
||||||
|
type="number" |
||||||
|
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]" |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
), |
||||||
|
select: ( |
||||||
|
<select |
||||||
|
type="text" |
||||||
|
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]" |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{Object.entries(props?.options || {}).map(([v, k]) => ( |
||||||
|
<option value={v} key={v}> |
||||||
|
{k} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
|
), |
||||||
|
file: ( |
||||||
|
<div className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA] flex justify-between bg-[#fff]"> |
||||||
|
<div className="w-full flex items-center"> |
||||||
|
<div |
||||||
|
className="w-fit w-24 text-center py-1 rounded bg-[#111] text-white text-sm cursor-pointer" |
||||||
|
onClick={props.chooseFile} |
||||||
|
> |
||||||
|
انتخاب فایل |
||||||
|
</div> |
||||||
|
{/* <div className="text-xs mt-2 text-right">{props.fileNmae}</div> */} |
||||||
|
{props.fileNmae && !props.loading && ( |
||||||
|
<div |
||||||
|
className="w-fit w-24 py-1 rounded bg-[#65A9FF] text-white text-sm mr-2 cursor-pointer" |
||||||
|
onClick={props.uploadHandler} |
||||||
|
> |
||||||
|
آپلود |
||||||
|
</div> |
||||||
|
)} |
||||||
|
{props.loading && ( |
||||||
|
<img src={loadingGiff} alt="loadingGiff" className="h-10 mr-2" /> |
||||||
|
)} |
||||||
|
<input |
||||||
|
className="edit-file-input w-0 h-0 hidden" |
||||||
|
id="edit-file-input" |
||||||
|
type={"file"} |
||||||
|
// onChange={(e) => {
|
||||||
|
// props.setFileName(e.target.value);
|
||||||
|
// }}
|
||||||
|
onChange={(e) => props.fileInputHandler(e)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
{props.defaultValue && !props?.fileNmae ? ( |
||||||
|
<img |
||||||
|
src={baseUrl + "/file/" + props.defaultValue} |
||||||
|
className="edit-file-img-placeholder h-10" |
||||||
|
/> |
||||||
|
) : null} |
||||||
|
{props?.fileNmae && ( |
||||||
|
<img src={props?.fileNmae} className="edit-file-img-placeholder h-10" /> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
), |
||||||
|
date: ( |
||||||
|
<div className="w-full"> |
||||||
|
<SimpleDatePicker returnValue={props.setDateInfo} mode={props.mode} /> |
||||||
|
</div> |
||||||
|
), |
||||||
|
}); |
||||||
|
|
||||||
|
const Filed = (props) => { |
||||||
|
return components(props)[props.type] || <div>{props.type}</div>; |
||||||
|
}; |
||||||
|
const Editor = ({ data, schema, update, add, loading,lastUpload,uploadFile }) => { |
||||||
|
const [info, setInfo] = useState(data ? { id: data?.original?.id } : {}); |
||||||
|
const changeInputHandler = (e, key) => { |
||||||
|
setInfo({ ...info, [key]: e.target.value }); |
||||||
|
}; |
||||||
|
useEffect(() => { |
||||||
|
if (lastUpload) { |
||||||
|
setInfo({ ...info, fileId: String(lastUpload) }); |
||||||
|
} |
||||||
|
}, [lastUpload]); |
||||||
|
|
||||||
|
const submitHandler = () => { |
||||||
|
if (data) update(info); |
||||||
|
else add(info); |
||||||
|
console.log(info); |
||||||
|
}; |
||||||
|
|
||||||
|
const [fileNmae, setFileName] = useState(null); |
||||||
|
const chooseFile = () => { |
||||||
|
document.querySelector(".edit-file-input").click(); |
||||||
|
}; |
||||||
|
const fileInputHandler = (e) => { |
||||||
|
console.log(e.target.value); |
||||||
|
if (e.target.files && e.target.files[0]) { |
||||||
|
var reader = new FileReader(); |
||||||
|
|
||||||
|
reader.onload = function (event) { |
||||||
|
setFileName(event.target.result); |
||||||
|
}; |
||||||
|
|
||||||
|
reader.readAsDataURL(e.target.files[0]); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const uploadHandler=()=>{ |
||||||
|
uploadFile({ |
||||||
|
file: document.querySelector("#edit-file-input").files[0], |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
const [dateInfo,setDateInfo]=useState(null); |
||||||
|
const datehandler=(value,mode)=>{ |
||||||
|
setDateInfo({ ...value,mode}); |
||||||
|
} |
||||||
|
useEffect(() => { |
||||||
|
console.log("::::::::::::::::::::::::::::::::::::::::::::-") |
||||||
|
console.log(dateInfo); |
||||||
|
if(dateInfo?.day){ |
||||||
|
setInfo({ |
||||||
|
...info, |
||||||
|
[dateInfo.mode]: moment( |
||||||
|
`${dateInfo?.year}/${dateInfo?.month}/${dateInfo?.day}` |
||||||
|
) |
||||||
|
.locale("en") |
||||||
|
.format("YYYY-MM-DD"), |
||||||
|
}); |
||||||
|
} |
||||||
|
}, [dateInfo]); |
||||||
|
return ( |
||||||
|
<div className="w-full py-3" key={data?.id || 0}> |
||||||
|
{console.log({ data })} |
||||||
|
<div className="w-full flex rtl flex-wrap items-center justify-between gap-y-6"> |
||||||
|
{schema?.map((item, index) => ( |
||||||
|
<div key={index} className="w-1/2 shrink-0 flex items-center "> |
||||||
|
<div className="w-1/6 text-sm ml-2">{item?.description}:</div> |
||||||
|
<Filed |
||||||
|
type={ |
||||||
|
item?.options |
||||||
|
? "select" |
||||||
|
: item.name == "fileId" |
||||||
|
? "file" |
||||||
|
: item?.type |
||||||
|
} |
||||||
|
options={item?.options} |
||||||
|
defaultValue={data?.original[item?.name]} |
||||||
|
onChange={(e) => changeInputHandler(e, item?.name)} |
||||||
|
fileNmae={fileNmae} |
||||||
|
setFileName={setFileName} |
||||||
|
chooseFile={chooseFile} |
||||||
|
fileInputHandler={fileInputHandler} |
||||||
|
uploadHandler={uploadHandler} |
||||||
|
loading={loading} |
||||||
|
setDateInfo={datehandler} |
||||||
|
mode={item?.name} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
<div |
||||||
|
className="px-6 py-4 w-fit rounded-md bg-[#3C58EB] text-[#fff] text-base cursor-pointer mx-auto mt-6" |
||||||
|
onClick={submitHandler} |
||||||
|
> |
||||||
|
{data ? "ثبت تغییرات" : "ثبت ردیف"} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({ |
||||||
|
loading: state.file.loading, |
||||||
|
lastUpload: state.file.lastUpload, |
||||||
|
}); |
||||||
|
|
||||||
|
const mapDispatchToProps = { |
||||||
|
uploadFile : file.upload |
||||||
|
}; |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Editor); |