index.tsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import React, { useEffect, useState } from 'react';
  2. import { View } from 'react-native';
  3. import { Modal } from '../../Modal';
  4. import DateTimePicker, { DateType } from 'react-native-ui-datepicker';
  5. import dayjs from 'dayjs';
  6. import 'dayjs/locale/en';
  7. import calendar from 'dayjs/plugin/calendar';
  8. import updateLocale from 'dayjs/plugin/updateLocale';
  9. import localeData from 'dayjs/plugin/localeData';
  10. import customParseFormat from 'dayjs/plugin/customParseFormat';
  11. dayjs.locale('en');
  12. dayjs.extend(calendar);
  13. dayjs.extend(updateLocale);
  14. dayjs.extend(localeData);
  15. dayjs.extend(customParseFormat);
  16. import { styles } from './style';
  17. import { Colors } from '../../../theme';
  18. import { Button } from 'src/components/Button';
  19. import { ButtonVariants } from 'src/types/components';
  20. import Navigation from './Navigation';
  21. export default function RangeCalendar({
  22. isModalVisible,
  23. closeModal,
  24. allowRangeSelection = true,
  25. disableFutureDates = false,
  26. disablePastDates = false,
  27. highlightedDates,
  28. selectedDate,
  29. minDate: externalMinDate,
  30. maxDate: externalMaxDate,
  31. initialStartDate,
  32. initialEndDate,
  33. initialYear,
  34. initialMonth
  35. }: {
  36. isModalVisible: boolean;
  37. closeModal: (startDate?: string | null, endDate?: string | null) => void;
  38. allowRangeSelection?: boolean;
  39. disableFutureDates?: boolean;
  40. disablePastDates?: boolean;
  41. highlightedDates?: string[];
  42. selectedDate?: string;
  43. minDate?: string | Date | dayjs.Dayjs;
  44. maxDate?: string | Date | dayjs.Dayjs;
  45. initialStartDate?: string | null;
  46. initialEndDate?: string | null;
  47. initialYear?: number;
  48. initialMonth?: number;
  49. }) {
  50. const [selectedStartDate, setSelectedStartDate] = useState<string | null>(null);
  51. const [selectedEndDate, setSelectedEndDate] = useState<string | null>(null);
  52. const [singleDate, setSingleDate] = useState<DateType>(undefined);
  53. const [startDate, setStartDate] = useState<DateType>(undefined);
  54. const [endDate, setEndDate] = useState<DateType>(undefined);
  55. const [currentMonth, setCurrentMonth] = useState<number | undefined>(undefined);
  56. const [currentYear, setCurrentYear] = useState<number | undefined>(undefined);
  57. const computedMinDate = externalMinDate
  58. ? dayjs(externalMinDate)
  59. : highlightedDates && highlightedDates.length > 0
  60. ? dayjs(highlightedDates[0])
  61. : undefined;
  62. const computedMaxDate = externalMaxDate
  63. ? dayjs(externalMaxDate)
  64. : highlightedDates && highlightedDates.length > 0
  65. ? dayjs(highlightedDates[highlightedDates.length - 1])
  66. : undefined;
  67. useEffect(() => {
  68. if (!isModalVisible) return;
  69. if (allowRangeSelection) {
  70. if (initialStartDate) {
  71. const startDateObj = dayjs(initialStartDate);
  72. setSelectedStartDate(initialStartDate);
  73. setStartDate(startDateObj);
  74. setCurrentMonth(startDateObj.month());
  75. setCurrentYear(startDateObj.year());
  76. } else {
  77. if (typeof initialYear === 'number' && initialYear !== 1) {
  78. setCurrentYear(initialYear);
  79. }
  80. if (typeof initialMonth === 'number') {
  81. setCurrentMonth(initialMonth - 1);
  82. }
  83. }
  84. if (initialEndDate) {
  85. setSelectedEndDate(initialEndDate);
  86. setEndDate(dayjs(initialEndDate));
  87. }
  88. } else if (selectedDate) {
  89. const dateObj = dayjs(selectedDate);
  90. setSelectedStartDate(selectedDate);
  91. setSingleDate(dateObj);
  92. setCurrentMonth(dateObj.month());
  93. setCurrentYear(dateObj.year());
  94. }
  95. }, [
  96. isModalVisible,
  97. initialStartDate,
  98. initialEndDate,
  99. selectedDate,
  100. allowRangeSelection,
  101. initialYear,
  102. initialMonth
  103. ]);
  104. const getDisabledDates = (date: DateType) => {
  105. const dateString = dayjs(date).format('YYYY-MM-DD');
  106. if (disableFutureDates) {
  107. if (dayjs(date).isAfter(dayjs(), 'day')) {
  108. return true;
  109. }
  110. }
  111. if (disablePastDates) {
  112. if (dayjs(date).isBefore(dayjs(), 'day')) {
  113. return true;
  114. }
  115. }
  116. if (highlightedDates && highlightedDates.length > 0) {
  117. return !highlightedDates.includes(dateString);
  118. }
  119. return false;
  120. };
  121. const handleDateChange = (params: any) => {
  122. if (allowRangeSelection) {
  123. setStartDate(params.startDate);
  124. setEndDate(params.endDate);
  125. if (params.startDate) {
  126. setSelectedStartDate(dayjs(params.startDate).format('YYYY-MM-DD'));
  127. }
  128. if (params.endDate) {
  129. setSelectedEndDate(dayjs(params.endDate).format('YYYY-MM-DD'));
  130. } else {
  131. setSelectedEndDate(null);
  132. }
  133. } else {
  134. setSingleDate(params.date);
  135. setSelectedStartDate(dayjs(params.date).format('YYYY-MM-DD'));
  136. setSelectedEndDate(null);
  137. }
  138. };
  139. const resetSelections = () => {
  140. closeModal();
  141. setSelectedStartDate(null);
  142. setSelectedEndDate(null);
  143. setStartDate(undefined);
  144. setEndDate(undefined);
  145. setSingleDate(undefined);
  146. setTimeout(() => {
  147. setCurrentMonth(undefined);
  148. setCurrentYear(undefined);
  149. }, 200);
  150. };
  151. const handleClose = () => {
  152. closeModal(selectedStartDate, selectedEndDate);
  153. setSelectedStartDate(null);
  154. setSelectedEndDate(null);
  155. setStartDate(undefined);
  156. setEndDate(undefined);
  157. setSingleDate(undefined);
  158. setTimeout(() => {
  159. setCurrentMonth(undefined);
  160. setCurrentYear(undefined);
  161. }, 200);
  162. };
  163. return (
  164. <Modal
  165. visibleInPercent={'auto'}
  166. visible={isModalVisible}
  167. onRequestClose={resetSelections}
  168. headerTitle={allowRangeSelection ? 'Select Dates' : 'Select Date'}
  169. >
  170. <View style={styles.modalContent}>
  171. <DateTimePicker
  172. key={`${currentMonth}-${currentYear}`}
  173. mode={allowRangeSelection ? 'range' : 'single'}
  174. date={singleDate}
  175. startDate={startDate}
  176. endDate={endDate}
  177. month={currentMonth}
  178. year={currentYear}
  179. onChange={handleDateChange}
  180. minDate={computedMinDate}
  181. maxDate={computedMaxDate}
  182. disabledDates={getDisabledDates}
  183. firstDayOfWeek={1}
  184. timePicker={false}
  185. showOutsideDays={true}
  186. weekdaysFormat={'short'}
  187. components={{
  188. IconPrev: <Navigation direction="prev" />,
  189. IconNext: <Navigation direction="next" />
  190. }}
  191. styles={{
  192. header: {
  193. paddingBottom: 10
  194. },
  195. month_selector_label: {
  196. color: Colors.DARK_BLUE,
  197. fontWeight: 'bold',
  198. fontSize: 15
  199. },
  200. year_selector_label: {
  201. color: Colors.DARK_BLUE,
  202. fontWeight: 'bold',
  203. fontSize: 15
  204. },
  205. button_prev: {},
  206. button_next: {},
  207. weekday_label: {
  208. color: Colors.DARK_BLUE,
  209. fontWeight: 'bold',
  210. fontSize: 12
  211. },
  212. days: {},
  213. day_cell: {},
  214. day: {},
  215. day_label: {
  216. color: Colors.DARK_BLUE,
  217. fontSize: 14,
  218. fontWeight: 'normal'
  219. },
  220. today: {
  221. borderWidth: 1,
  222. borderRadius: 23,
  223. width: 46,
  224. height: 46,
  225. maxHeight: 46,
  226. borderColor: Colors.ORANGE
  227. },
  228. today_label: {
  229. color: Colors.DARK_BLUE,
  230. fontWeight: '500'
  231. },
  232. selected: {
  233. backgroundColor: Colors.ORANGE,
  234. borderRadius: 23,
  235. width: 46,
  236. height: 46,
  237. maxHeight: 46,
  238. marginVertical: 'auto',
  239. alignItems: 'center',
  240. justifyContent: 'center'
  241. },
  242. selected_label: {
  243. color: 'white',
  244. fontWeight: '500'
  245. },
  246. range_start: {
  247. backgroundColor: Colors.ORANGE,
  248. borderRadius: 23,
  249. width: 46,
  250. height: 46,
  251. alignItems: 'center',
  252. justifyContent: 'center'
  253. },
  254. range_start_label: {
  255. color: 'white'
  256. },
  257. range_end: {
  258. backgroundColor: Colors.ORANGE,
  259. borderRadius: 23,
  260. width: 46,
  261. height: 46,
  262. alignItems: 'center',
  263. justifyContent: 'center'
  264. },
  265. range_end_label: {
  266. color: 'white'
  267. },
  268. range_middle_label: {
  269. color: Colors.DARK_BLUE,
  270. fontWeight: '500'
  271. },
  272. range_fill: {
  273. backgroundColor: Colors.ORANGE,
  274. opacity: 0.2
  275. },
  276. disabled: {
  277. opacity: 0.3
  278. },
  279. disabled_label: {
  280. color: Colors.TEXT_GRAY
  281. },
  282. outside_label: {
  283. color: Colors.LIGHT_GRAY
  284. }
  285. }}
  286. />
  287. </View>
  288. <View style={styles.modalFooter}>
  289. <Button
  290. children="Done"
  291. onPress={handleClose}
  292. disabled={!selectedStartDate}
  293. variant={!selectedStartDate ? ButtonVariants.OPACITY : ButtonVariants.FILL}
  294. containerStyles={{ borderWidth: 0 }}
  295. />
  296. </View>
  297. </Modal>
  298. );
  299. }