datepicker.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import React from "react"
  2. import ReactDOM from "react-dom"
  3. import moment from "moment"
  4. const initDatepicker = ({ elementId, never, setDate }) => {
  5. const element = document.getElementById(elementId)
  6. if (!element) console.error("Element with id " + element + "doesn't exist!")
  7. element.type = "hidden"
  8. const name = element.name
  9. const value = element.value.length ? moment(element.value) : null
  10. if (value) value.local()
  11. const container = document.createElement("div")
  12. element.parentNode.insertBefore(container, element)
  13. element.remove()
  14. ReactDOM.render(
  15. <DatePicker name={name} never={never} value={value} setDate={setDate} />,
  16. container
  17. )
  18. }
  19. class DatePicker extends React.Component {
  20. state = {
  21. defaultValue: this.props.value,
  22. value: this.props.value
  23. }
  24. setNever = () => {
  25. this.setState({ value: null })
  26. }
  27. setInitialValue = () => {
  28. this.setState(({ defaultValue, value }) => {
  29. if (defaultValue) return { value: defaultValue }
  30. const newValue = moment()
  31. newValue.add(1, "hour")
  32. return { value: newValue }
  33. })
  34. }
  35. setValue = value => {
  36. this.setState({ value })
  37. }
  38. render() {
  39. const { name, never, setDate } = this.props
  40. const { value } = this.state
  41. return (
  42. <div onBlur={this.handleBlur} onFocus={this.handleFocus}>
  43. <input type="hidden" name={name} value={value ? value.format() : ""} />
  44. <div>
  45. <button
  46. className={getButtonClassName(value === null)}
  47. type="button"
  48. onClick={this.setNever}
  49. >
  50. {never}
  51. </button>
  52. <button
  53. className={getButtonClassName(value !== null) + " ml-3"}
  54. type="button"
  55. onClick={this.setInitialValue}
  56. >
  57. {value ? value.format("L LT") : setDate}
  58. </button>
  59. </div>
  60. <Input value={value} onChange={this.setValue} />
  61. </div>
  62. )
  63. }
  64. }
  65. const getButtonClassName = active => {
  66. if (active) return "btn btn-outline-primary btn-sm"
  67. return "btn btn-outline-secondary btn-sm"
  68. }
  69. const Input = ({ value, onChange }) => {
  70. if (!value) return null
  71. return (
  72. <div className="row mt-3">
  73. <div className="col-auto">
  74. <SelectMonth value={value} onChange={onChange} />
  75. </div>
  76. <div className="col-auto">
  77. <SelectTime value={value} onChange={onChange} />
  78. </div>
  79. </div>
  80. )
  81. }
  82. const weeks = [1, 2, 3, 4, 5, 6]
  83. const days = [1, 2, 3, 4, 5, 6, 7]
  84. class SelectMonth extends React.Component {
  85. decreaseMonth = () => {
  86. this.setState((_, props) => {
  87. const value = props.value.clone()
  88. value.subtract(1, "month")
  89. props.onChange(value)
  90. })
  91. }
  92. increaseMonth = () => {
  93. this.setState((_, props) => {
  94. const value = props.value.clone()
  95. value.add(1, "month")
  96. props.onChange(value)
  97. })
  98. }
  99. render() {
  100. const { value, onChange } = this.props
  101. const startOfMonth = value
  102. .clone()
  103. .startOf("month")
  104. .isoWeekday()
  105. const calendar = value.clone()
  106. calendar.date(1)
  107. calendar.hour(value.hour())
  108. calendar.minute(value.minute())
  109. calendar.subtract(startOfMonth + 1, "day")
  110. return (
  111. <div className="control-month-picker">
  112. <CalendarHeader
  113. decreaseMonth={this.decreaseMonth}
  114. increaseMonth={this.increaseMonth}
  115. value={value}
  116. />
  117. <WeekdaysNames />
  118. {weeks.map(w => (
  119. <div className="row align-items-center m-0" key={w}>
  120. {days.map(d => (
  121. <Weekday
  122. calendar={calendar}
  123. key={d}
  124. value={value}
  125. onSelect={onChange}
  126. />
  127. ))}
  128. </div>
  129. ))}
  130. </div>
  131. )
  132. }
  133. }
  134. const CalendarHeader = ({ decreaseMonth, increaseMonth, value }) => (
  135. <div className="row align-items-center">
  136. <div className="col-auto text-center">
  137. <button
  138. className="btn btn-block py-1 px-3"
  139. type="button"
  140. onClick={decreaseMonth}
  141. >
  142. <span className="fas fa-chevron-left" />
  143. </button>
  144. </div>
  145. <div className="col text-center font-weight-bold">
  146. {value.format("MMMM YYYY")}
  147. </div>
  148. <div className="col-auto text-center">
  149. <button
  150. className="btn btn-block py-1 px-3"
  151. type="button"
  152. onClick={increaseMonth}
  153. >
  154. <span className="fas fa-chevron-right" />
  155. </button>
  156. </div>
  157. </div>
  158. )
  159. const WeekdaysNames = () => (
  160. <div className="row align-items-center m-0">
  161. {moment.weekdaysMin(false).map((name, i) => (
  162. <div
  163. className={
  164. "col text-center px-1 " + (i === 0 ? "text-danger" : "text-muted")
  165. }
  166. key={name}
  167. >
  168. {name}
  169. </div>
  170. ))}
  171. </div>
  172. )
  173. const Weekday = ({ calendar, value, onSelect }) => {
  174. calendar.add(1, "day")
  175. const day = calendar.clone()
  176. const active = day.format("D M Y") === value.format("D M Y")
  177. return (
  178. <div className={"col text-center px-1"}>
  179. <button
  180. className={"btn btn-sm btn-block px-0" + (active ? " btn-primary" : "")}
  181. type="button"
  182. onClick={() => onSelect(day)}
  183. disabled={day.month() !== value.month()}
  184. >
  185. {day.format("D")}
  186. </button>
  187. </div>
  188. )
  189. }
  190. class SelectTime extends React.Component {
  191. handleHourChange = ({ target }) => {
  192. const { value: time } = target
  193. if (!time.match(/^[0-2][0-9]?[0-9]?$/)) return
  194. this.setState((_, props) => {
  195. const hour = cleanTimeValue(time, 2)
  196. const value = props.value.clone()
  197. value.hour(hour)
  198. props.onChange(value)
  199. })
  200. }
  201. handleMinuteChange = ({ target }) => {
  202. const { value: time } = target
  203. if (!time.match(/^[0-5][0-9]?[0-9]?$/)) return
  204. this.setState((_, props) => {
  205. const minute = cleanTimeValue(time, 5)
  206. const value = props.value.clone()
  207. value.minute(minute)
  208. props.onChange(value)
  209. })
  210. }
  211. render() {
  212. return (
  213. <div className="control-time-picker">
  214. <div className="row align-items-center m-0">
  215. <div className="col px-0">
  216. <TimeInput
  217. format="HH"
  218. value={this.props.value}
  219. onChange={this.handleHourChange}
  220. />
  221. </div>
  222. <div className="col-auto px-0">
  223. <span>:</span>
  224. </div>
  225. <div className="col px-0">
  226. <TimeInput
  227. format="mm"
  228. value={this.props.value}
  229. onChange={this.handleMinuteChange}
  230. />
  231. </div>
  232. </div>
  233. </div>
  234. )
  235. }
  236. }
  237. const cleanTimeValue = (time, maxFirstDigit) => {
  238. let value = time
  239. if (value.length === 3) {
  240. value = value.substring(1, 3)
  241. if (parseInt(value[0]) > maxFirstDigit) {
  242. value = maxFirstDigit + "" + value[1]
  243. }
  244. }
  245. return value
  246. }
  247. const TimeInput = ({ format, value, onChange }) => (
  248. <input
  249. className="form-control text-center"
  250. placeholder="00"
  251. type="text"
  252. value={value.format(format)}
  253. onChange={onChange}
  254. />
  255. )
  256. export default initDatepicker