calendar.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { View, Row } from 'app/design/view'
  2. import Dropdown from 'app/ui/atoms/dropdown'
  3. import { useState, useReducer, useMemo, useEffect } from 'react';
  4. import { Modal } from 'app/design/controls'
  5. import { NeoButton } from 'app/design/controls';
  6. import { Text } from 'app/design/typography';
  7. import { Icon } from 'app/ui/atoms/icon';
  8. import { useTheme } from 'app/design/theme';
  9. import { BlockWrapper } from 'app/components/block-wrapper'
  10. import i18n from 'i18next'
  11. import { useTranslation } from 'react-i18next'
  12. const formatValueDate = (v) => {
  13. const date = new Date(v.dt);
  14. const localDate = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
  15. return localDate.toLocaleDateString();
  16. }
  17. const formatValue = (v, bIsTime) => {
  18. if (v.dt != '') {
  19. let v3 = formatValueDate(v);
  20. if (bIsTime)
  21. return `${v3} ${v.h}:${v.m}`
  22. else
  23. return v3;
  24. }
  25. else {
  26. return bIsTime ? i18n.t('Select date/time') : i18n.t('Select date');
  27. }
  28. }
  29. const setdValue_ = (state, action) => {
  30. return { ...state, [action.type]: action.value };
  31. }
  32. const loadCalendar = async (setDynamicCalendar) => {
  33. const Calendars = await import('react-native-calendars');
  34. setDynamicCalendar(() => Calendars.Calendar);
  35. };
  36. const CalendarHeader = (dValue, addMonth) => {
  37. if (dValue.dt == ''){
  38. return (<Row className='w-full justify-center mb-4 items-center mt-2'><Text className=" font-medium text-muted-foreground text-lg">{i18n.t('Select date')}</Text></Row>)
  39. }
  40. return (<Row className='w-full justify-between gap-2 mb-4 items-center mt-2'>
  41. <NeoButton controlSize="small" borderShape="circle" image="ChevronsLeft" accessibilityLabel={i18n.t('Previous year')} classNames={{ root: 'self-center' }} onPress={() => addMonth('y', -1)} />
  42. <NeoButton controlSize="small" borderShape="circle" image="ChevronLeft" accessibilityLabel={i18n.t('Previous month')} classNames={{ root: 'self-center' }} onPress={() => addMonth('m', -1)} />
  43. <Text className=" font-medium text-muted-foreground text-lg">{formatValueDate(dValue)}</Text>
  44. <NeoButton controlSize="small" borderShape="circle" image="ChevronRight" accessibilityLabel={i18n.t('Next month')} classNames={{ root: 'self-center' }} onPress={() => addMonth('m', 1)} />
  45. <NeoButton controlSize="small" borderShape="circle" image="ChevronsRight" accessibilityLabel={i18n.t('Next year')} classNames={{ root: 'self-center' }} onPress={() => addMonth('y', 1)} />
  46. </Row>)
  47. };
  48. const generateValues = range => Array.from({ length: range }, (_, i) => ({ label: i.toString().padStart(2, '0'), value: i.toString().padStart(2, '0') }));
  49. export default function ({ name, value = '', type, onChange, blockWrapperProps }) {
  50. const { t } = useTranslation();
  51. const { colors } = useTheme();
  52. const bIsTime = type === 'datetime';
  53. const [showModal, setShowModal] = useState(false);
  54. const [DynamicCalendar, setDynamicCalendar] = useState(null);
  55. const [date, hour = '00', minute = '00'] = value.split(/[: ]/);
  56. const [dValue, setdValue] = useReducer(setdValue_, { dt: date, h: hour, m: minute });
  57. const [cValue, setcValue] = useState({ dt: date, h: hour, m: minute });
  58. useEffect(() => {
  59. let isMounted = true;
  60. loadCalendar(setDynamicCalendar).catch(console.error).then(() => {
  61. if (!isMounted) setDynamicCalendar(null);
  62. });
  63. return () => { isMounted = false; };
  64. }, []);
  65. const setFieldValue = (val, hide = true) => {
  66. setcValue(val)
  67. onChange((new Date(`${val.dt} ${val.h}:${val.m}`).getTime()) / 1000)
  68. if (hide)
  69. setShowModal(false);
  70. }
  71. const setValueDay = (day, hide = true) => {
  72. setdValue({ type: 'dt', value: day.dateString })
  73. if (!bIsTime) {
  74. setFieldValue({ dt: day.dateString, h: dValue.h, m: dValue.m }, hide);
  75. }
  76. };
  77. const addMonth = (type, val) => {
  78. let newDate = new Date(dValue.dt);
  79. if (type === 'y')
  80. newDate.setFullYear(newDate.getFullYear() + val);
  81. if (type === 'm')
  82. newDate.setMonth(newDate.getMonth() + val);
  83. setValueDay({ dateString: newDate.toISOString().slice(0, 10) }, false)
  84. };
  85. const hours = useMemo(() => generateValues(24), []);
  86. const minutes = useMemo(() => generateValues(60), []);
  87. return (
  88. <BlockWrapper {...blockWrapperProps}>
  89. <Modal onVisible={!!showModal} onClose={() => { setShowModal(false) }} transparent={false}>
  90. <View className=' max-w-sm w-full mx-auto'>
  91. <View className=' max-w-sm w-full mx-auto '>
  92. {DynamicCalendar && <DynamicCalendar
  93. className=' bg-card'
  94. theme={{
  95. calendarBackground: colors.background2,
  96. dayTextColor: colors.text,
  97. textDisabledColor: colors.text,
  98. monthTextColor: colors.text,
  99. }}
  100. renderArrow={direction => { return <View className="text-secondary-foreground "><Icon icon={direction == 'left' ? 'ArrowLeft' : 'ArrowRight'} width={24} height={24} /></View> }}
  101. initialDate={dValue.dt}
  102. customHeader={() => CalendarHeader(dValue, addMonth)}
  103. onDayPress={day => {
  104. setValueDay(day)
  105. }}
  106. markedDates={{
  107. [dValue.dt]: { selected: true, selectedColor: colors.primary }
  108. }}
  109. />}
  110. </View>
  111. {
  112. bIsTime && (<View className='w-full justify-center items-center gap-y-4'><Row className='justify-center items-center w-64 mt-2'>
  113. <Text className=" text-base justify-center items-center text-popover-foreground "> {t('Time')} </Text>
  114. <View>
  115. <Dropdown
  116. labelField="label"
  117. valueField="value"
  118. onChange={(v) => setdValue({ type: 'h', value: v })}
  119. value={dValue.h}
  120. data={hours}
  121. />
  122. </View>
  123. <Text className=" text-base justify-center items-center text-popover-foreground "> : </Text>
  124. <View>
  125. <Dropdown
  126. labelField="label"
  127. valueField="value"
  128. onChange={(v) => setdValue({ type: 'm', value: v })}
  129. value={dValue.m}
  130. data={minutes}
  131. />
  132. </View>
  133. </Row>
  134. <NeoButton label={t('Apply')} classNames={{ root: 'self-center' }} onPress={() => { setFieldValue(dValue) }} />
  135. </View>
  136. )
  137. }
  138. </View>
  139. </Modal>
  140. <Row>
  141. <NeoButton label={formatValue(cValue, bIsTime)} image="Calendar" imagePlacement="trailing" onPress={() => { setShowModal(true) }} />
  142. </Row>
  143. </BlockWrapper>
  144. );
  145. }