Merge pull request #3 from tomru/react-day-picker

move to react-day-picker
This commit is contained in:
Thomas Ruoff
2020-08-11 23:00:37 +02:00
committed by GitHub
15 changed files with 4269 additions and 330 deletions

3
.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": ["next/babel"]
}

View File

@@ -1,23 +1,18 @@
{ {
"env": { "env": {
"browser": true, "browser": true,
"es2020": true, "es2020": true,
"node": true "node": true,
"jest": true
},
"extends": ["eslint:recommended", "plugin:react/recommended"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}, },
"extends": [ "ecmaVersion": 11,
"eslint:recommended", "sourceType": "module"
"plugin:react/recommended" },
], "plugins": ["react"],
"parserOptions": { "rules": {}
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
}
} }

View File

@@ -1,5 +1,5 @@
import { useContext } from 'react' import React, { useContext } from 'react'
import { WizardContext, ACTIONS } from '../context/wizardStore' import { WizardContext } from '../context/wizardStore'
import Required from './required' import Required from './required'
import Form from 'react-bootstrap/Form' import Form from 'react-bootstrap/Form'

View File

@@ -1,35 +1,69 @@
import { useContext, useState } from 'react' import React, { useContext, useState, useRef, useEffect } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { WizardContext, ACTIONS } from '../context/wizardStore' import { WizardContext } from '../context/wizardStore'
import Form from 'react-bootstrap/Form' import Form from 'react-bootstrap/Form'
import Button from 'react-bootstrap/Button'
import moment from 'moment' import { DateUtils } from 'react-day-picker'
import 'react-dates/initialize' import DayPickerInput from 'react-day-picker/DayPickerInput'
import { DateRangePicker, SingleDatePicker } from 'react-dates'
import Required from './required' import Required from './required'
import { dateFormat } from '../helpers/date' import { dateFormat } from '../helpers/date'
import { getNextSmaller, getNextBigger } from '../helpers/array'
const fetcher = (path) => fetch(path).then((r) => r.json()) const fetcher = (path) => fetch(path).then((r) => r.json())
export default function DateSelect() { export default function DateSelect() {
const [focusedInput, setFocusedInput] = useState(null)
const { state, onChange } = useContext(WizardContext) const { state, onChange } = useContext(WizardContext)
const [range, setRange] = useState({
form: state.startDate && new Date(state.StartDate),
to: state.endDate && new Date(state.endDate),
})
const { from, to } = range
const { data: daysBooked, error: fetchBookedOnError } = useSWR( const { data: daysBooked, error: fetchBookedOnError } = useSWR(
'/api/daysbooked', '/api/daysbooked',
fetcher fetcher
) )
const prevBookedDay = getNextSmaller(daysBooked, dateFormat(from || to))
const nextBookedDay = getNextBigger(daysBooked, dateFormat(from || to))
const { multipleDays, startDate, endDate } = state.formData const fromRef = useRef()
const toRef = useRef()
function isDayBlocked(momentDay) { function dayBooked(day) {
return daysBooked && daysBooked.includes(dateFormat(day))
}
function dayDisabled(day) {
return ( return (
daysBooked && daysBooked.some((rawDay) => momentDay.isSame(rawDay, 'day')) DateUtils.isPastDay(day) ||
dayBooked(day) ||
(prevBookedDay && day < new Date(prevBookedDay)) ||
(nextBookedDay && day > new Date(nextBookedDay))
) )
} }
function parseDate(value) {
return new Date(value)
}
useEffect(() => {
toRef.current?.getInput().focus()
}, [from])
useEffect(() => {
onChange({ startDate: from?.toISOString(), endDate: to?.toISOString() })
}, [from, to])
const disabledDays = [dayDisabled]
const modifiers = {
dayBooked,
start: from,
end: to,
}
if (fetchBookedOnError) { if (fetchBookedOnError) {
return ( return (
<div> <div>
@@ -40,92 +74,55 @@ export default function DateSelect() {
} }
return ( return (
<> <Form.Group>
<Form.Group controlId="dateSelect"> <Form.Label component="legend" style={{ display: 'block' }}>
<Form.Label> Datum <Required />
Willst du einen odere mehrere Tage buchen? <Required /> </Form.Label>
</Form.Label> <Form.Group>
<Form.Check <DayPickerInput
type="radio" ref={fromRef}
id={'multipleDays-single'} component={Form.Control}
label="Einen Tag" value={from}
name="multipleDays" placeholder="Von"
value="single" formatDate={dateFormat}
checked={multipleDays === false} parseDate={parseDate}
onChange={() => { dayPickerProps={{
setFocusedInput(null) className: 'Selectable',
onChange({ selectedDays: [from, { from, to }],
multipleDays: false, disabledDays,
startDate: null, modifiers,
endDate: null, numberOfMonths: 1,
})
}} }}
onDayChange={(from) => setRange({ ...range, from })}
/> />
<Form.Check <Form.Label className="px-2">bis</Form.Label>
type="radio" <DayPickerInput
id={'multipleDays-multiple'} ref={toRef}
label="Mehrere Tage" component={Form.Control}
name="multipleDays" inputProps={{ disabled: !from }}
value="multiple" value={to}
checked={multipleDays === true} placeholder="Bis"
onChange={() => { formatDate={dateFormat}
setFocusedInput(null) parseDate={parseDate}
onChange({ dayPickerProps={{
multipleDays: true, className: 'Selectable',
startDate: null, selectedDays: [from, { from, to }],
endDate: null, disabledDays,
}) modifiers,
numberOfMonths: 1,
month: from,
fromMonth: from,
}} }}
onDayChange={(to) => setRange({ ...range, to })}
/> />
<Button
className="ml-2"
variant="outline-secondary"
onClick={() => setRange({})}
>
Zurücksetzen
</Button>
</Form.Group> </Form.Group>
{multipleDays !== null && ( </Form.Group>
<Form.Group>
<Form.Label component="legend" style={{ display: 'block' }}>
Datum <Required />
</Form.Label>
{multipleDays === false && (
<SingleDatePicker
small={true}
date={startDate && moment(startDate)}
placeholder="Datum"
numberOfMonths={1}
required
onDateChange={(date) =>
onChange({
startDate: date && dateFormat(date),
})
}
focused={typeof focusedInput === 'boolean' && focusedInput}
onFocusChange={({ focused }) => setFocusedInput(focused)}
isDayBlocked={isDayBlocked}
id="startDate"
/>
)}
{multipleDays === true && (
<DateRangePicker
small={true}
startDate={startDate && moment(startDate)}
startDateId="startDate"
startDatePlaceholderText="Start"
endDatePlaceholderText="Ende"
required
numberOfMonths={1}
endDate={endDate && moment(endDate)}
endDateId="endDate"
onDatesChange={({ startDate, endDate }) => {
onChange({
startDate: startDate && dateFormat(startDate),
endDate: endDate && dateFormat(endDate),
})
}}
focusedInput={focusedInput}
onFocusChange={(focusedInput) => setFocusedInput(focusedInput)}
isDayBlocked={isDayBlocked}
minDate={moment()}
/>
)}
</Form.Group>
)}
</>
) )
} }

View File

@@ -1,5 +1,5 @@
import { useContext } from 'react' import React, { useContext } from 'react'
import { WizardContext, ACTIONS } from '../context/wizardStore' import { WizardContext } from '../context/wizardStore'
import Required from './required' import Required from './required'
import Form from 'react-bootstrap/Form' import Form from 'react-bootstrap/Form'

View File

@@ -1,2 +1,4 @@
import React from 'react'
const Required = () => <span>*</span> const Required = () => <span>*</span>
export default Required export default Required

View File

@@ -1,4 +1,4 @@
import { useContext } from 'react' import React, { useContext } from 'react'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form' import Form from 'react-bootstrap/Form'
@@ -28,9 +28,6 @@ function WizardInternal() {
) )
} }
const onChange = (payload) =>
dispatch({ action: ACTIONS.SET_FORM_DATA, payload })
return ( return (
<Form <Form
onSubmit={(event) => { onSubmit={(event) => {

View File

@@ -63,7 +63,6 @@ const initialState = {
postDataError: null, postDataError: null,
postDataSuccess: null, postDataSuccess: null,
formData: { formData: {
multipleDays: true,
//startDate: '2020-08-10', //startDate: '2020-08-10',
//endDate: '2020-08-17', //endDate: '2020-08-17',
//purpose: 'Sommerlager 2021', //purpose: 'Sommerlager 2021',

View File

@@ -30,7 +30,10 @@ export async function getBookedDays() {
'startDate endDate' 'startDate endDate'
).exec() ).exec()
return bookings.map((booking) => booking.days).flat() return bookings
.map((booking) => booking.days)
.flat()
.sort()
} }
export async function createBooking({ export async function createBooking({

18
helpers/array.js Normal file
View File

@@ -0,0 +1,18 @@
export function getNextSmaller(array, pivot) {
if (!array || !Array.isArray(array) || !array.length) {
return null
}
return array
.sort()
.reverse()
.find((item) => item < pivot)
}
export function getNextBigger(array, pivot) {
if (!array || !Array.isArray(array) || !array.length) {
return null
}
return array.sort().find((day) => day > pivot)
}

13
helpers/array.test.js Normal file
View File

@@ -0,0 +1,13 @@
import { getNextSmaller, getNextBigger } from './array'
test('getPreviousInOrder', () => {
const result = getNextSmaller([3, 1, 2, 0, 8, 9, 10], 5)
expect(result).toEqual(3)
})
test('getNextInOrder', () => {
const result = getNextBigger([7, 8, 9, 3, 1, 2], 4)
expect(result).toEqual(7)
})

4291
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,9 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"test:watch": "jest --watch",
"test:ci": "jest"
}, },
"dependencies": { "dependencies": {
"bootstrap": "^4.5.0", "bootstrap": "^4.5.0",
@@ -14,13 +16,15 @@
"next": "^9.5.0", "next": "^9.5.0",
"react": "16.13.1", "react": "16.13.1",
"react-bootstrap": "^1.3.0", "react-bootstrap": "^1.3.0",
"react-dates": "^21.8.0", "react-day-picker": "^7.4.8",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"rebass": "^4.0.7", "rebass": "^4.0.7",
"swr": "^0.2.3" "swr": "^0.2.3"
}, },
"devDependencies": { "devDependencies": {
"babel-jest": "^26.2.2",
"eslint": "^7.5.0", "eslint": "^7.5.0",
"eslint-plugin-react": "^7.20.5" "eslint-plugin-react": "^7.20.5",
"jest": "^26.2.2"
} }
} }

View File

@@ -1,5 +1,7 @@
import React from 'react'
import 'bootstrap/dist/css/bootstrap.min.css' import 'bootstrap/dist/css/bootstrap.min.css'
import 'react-dates/lib/css/_datepicker.css' import 'react-day-picker/lib/style.css'
import 'moment' import 'moment'
import 'moment/locale/de' import 'moment/locale/de'

View File

@@ -1,3 +1,5 @@
import React from 'react'
import Head from 'next/head' import Head from 'next/head'
import Wizard from '../components/wizard' import Wizard from '../components/wizard'
@@ -22,8 +24,6 @@ export default function Home() {
<a>Freundeskreis des VCP Rosenfeld</a> <a>Freundeskreis des VCP Rosenfeld</a>
</footer> </footer>
<style jsx>{``}</style>
<style jsx global>{` <style jsx global>{`
html, html,
body { body {
@@ -37,6 +37,27 @@ export default function Home() {
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
.Selectable
.DayPicker-Day--selected:not(.DayPicker-Day--start):not(.DayPicker-Day--end):not(.DayPicker-Day--outside) {
background-color: #f0f8ff !important;
color: #4a90e2;
}
.Selectable .DayPicker-Day {
border-radius: 0 !important;
}
.Selectable .DayPicker-Day--start {
border-top-left-radius: 50% !important;
border-bottom-left-radius: 50% !important;
}
.Selectable .DayPicker-Day--end {
border-top-right-radius: 50% !important;
border-bottom-right-radius: 50% !important;
}
.Selectable .DayPicker-Day--dayBooked:not(.DayPicker-Day--outside) {
color: #505050;
background-color: #fcc;
}
`}</style> `}</style>
</div> </div>
) )