mirror of
https://github.com/tomru/pfadi-bussle.git
synced 2026-03-03 06:27:11 +01:00
Merge pull request #3 from tomru/react-day-picker
move to react-day-picker
This commit is contained in:
@@ -1,23 +1,18 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true,
|
||||
"node": true
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react"],
|
||||
"rules": {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext } from 'react'
|
||||
import { WizardContext, ACTIONS } from '../context/wizardStore'
|
||||
import React, { useContext } from 'react'
|
||||
import { WizardContext } from '../context/wizardStore'
|
||||
import Required from './required'
|
||||
|
||||
import Form from 'react-bootstrap/Form'
|
||||
|
||||
@@ -1,35 +1,69 @@
|
||||
import { useContext, useState } from 'react'
|
||||
import React, { useContext, useState, useRef, useEffect } from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import { WizardContext, ACTIONS } from '../context/wizardStore'
|
||||
import { WizardContext } from '../context/wizardStore'
|
||||
|
||||
import Form from 'react-bootstrap/Form'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
|
||||
import moment from 'moment'
|
||||
import 'react-dates/initialize'
|
||||
import { DateRangePicker, SingleDatePicker } from 'react-dates'
|
||||
import { DateUtils } from 'react-day-picker'
|
||||
import DayPickerInput from 'react-day-picker/DayPickerInput'
|
||||
|
||||
import Required from './required'
|
||||
import { dateFormat } from '../helpers/date'
|
||||
import { getNextSmaller, getNextBigger } from '../helpers/array'
|
||||
|
||||
const fetcher = (path) => fetch(path).then((r) => r.json())
|
||||
|
||||
export default function DateSelect() {
|
||||
const [focusedInput, setFocusedInput] = useState(null)
|
||||
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(
|
||||
'/api/daysbooked',
|
||||
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 (
|
||||
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) {
|
||||
return (
|
||||
<div>
|
||||
@@ -40,92 +74,55 @@ export default function DateSelect() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Group controlId="dateSelect">
|
||||
<Form.Label>
|
||||
Willst du einen odere mehrere Tage buchen? <Required />
|
||||
</Form.Label>
|
||||
<Form.Check
|
||||
type="radio"
|
||||
id={'multipleDays-single'}
|
||||
label="Einen Tag"
|
||||
name="multipleDays"
|
||||
value="single"
|
||||
checked={multipleDays === false}
|
||||
onChange={() => {
|
||||
setFocusedInput(null)
|
||||
onChange({
|
||||
multipleDays: false,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
})
|
||||
<Form.Group>
|
||||
<Form.Label component="legend" style={{ display: 'block' }}>
|
||||
Datum <Required />
|
||||
</Form.Label>
|
||||
<Form.Group>
|
||||
<DayPickerInput
|
||||
ref={fromRef}
|
||||
component={Form.Control}
|
||||
value={from}
|
||||
placeholder="Von"
|
||||
formatDate={dateFormat}
|
||||
parseDate={parseDate}
|
||||
dayPickerProps={{
|
||||
className: 'Selectable',
|
||||
selectedDays: [from, { from, to }],
|
||||
disabledDays,
|
||||
modifiers,
|
||||
numberOfMonths: 1,
|
||||
}}
|
||||
onDayChange={(from) => setRange({ ...range, from })}
|
||||
/>
|
||||
<Form.Check
|
||||
type="radio"
|
||||
id={'multipleDays-multiple'}
|
||||
label="Mehrere Tage"
|
||||
name="multipleDays"
|
||||
value="multiple"
|
||||
checked={multipleDays === true}
|
||||
onChange={() => {
|
||||
setFocusedInput(null)
|
||||
onChange({
|
||||
multipleDays: true,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
})
|
||||
<Form.Label className="px-2">bis</Form.Label>
|
||||
<DayPickerInput
|
||||
ref={toRef}
|
||||
component={Form.Control}
|
||||
inputProps={{ disabled: !from }}
|
||||
value={to}
|
||||
placeholder="Bis"
|
||||
formatDate={dateFormat}
|
||||
parseDate={parseDate}
|
||||
dayPickerProps={{
|
||||
className: 'Selectable',
|
||||
selectedDays: [from, { from, to }],
|
||||
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>
|
||||
{multipleDays !== null && (
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
</Form.Group>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext } from 'react'
|
||||
import { WizardContext, ACTIONS } from '../context/wizardStore'
|
||||
import React, { useContext } from 'react'
|
||||
import { WizardContext } from '../context/wizardStore'
|
||||
import Required from './required'
|
||||
|
||||
import Form from 'react-bootstrap/Form'
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
import React from 'react'
|
||||
|
||||
const Required = () => <span>*</span>
|
||||
export default Required
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useContext } from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
|
||||
import Button from 'react-bootstrap/Button'
|
||||
import Form from 'react-bootstrap/Form'
|
||||
@@ -28,9 +28,6 @@ function WizardInternal() {
|
||||
)
|
||||
}
|
||||
|
||||
const onChange = (payload) =>
|
||||
dispatch({ action: ACTIONS.SET_FORM_DATA, payload })
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={(event) => {
|
||||
|
||||
@@ -63,7 +63,6 @@ const initialState = {
|
||||
postDataError: null,
|
||||
postDataSuccess: null,
|
||||
formData: {
|
||||
multipleDays: true,
|
||||
//startDate: '2020-08-10',
|
||||
//endDate: '2020-08-17',
|
||||
//purpose: 'Sommerlager 2021',
|
||||
|
||||
@@ -30,7 +30,10 @@ export async function getBookedDays() {
|
||||
'startDate endDate'
|
||||
).exec()
|
||||
|
||||
return bookings.map((booking) => booking.days).flat()
|
||||
return bookings
|
||||
.map((booking) => booking.days)
|
||||
.flat()
|
||||
.sort()
|
||||
}
|
||||
|
||||
export async function createBooking({
|
||||
|
||||
18
helpers/array.js
Normal file
18
helpers/array.js
Normal 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
13
helpers/array.test.js
Normal 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
4291
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -5,7 +5,9 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
"start": "next start",
|
||||
"test:watch": "jest --watch",
|
||||
"test:ci": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.5.0",
|
||||
@@ -14,13 +16,15 @@
|
||||
"next": "^9.5.0",
|
||||
"react": "16.13.1",
|
||||
"react-bootstrap": "^1.3.0",
|
||||
"react-dates": "^21.8.0",
|
||||
"react-day-picker": "^7.4.8",
|
||||
"react-dom": "16.13.1",
|
||||
"rebass": "^4.0.7",
|
||||
"swr": "^0.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^26.2.2",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-react": "^7.20.5"
|
||||
"eslint-plugin-react": "^7.20.5",
|
||||
"jest": "^26.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
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/locale/de'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react'
|
||||
|
||||
import Head from 'next/head'
|
||||
|
||||
import Wizard from '../components/wizard'
|
||||
@@ -22,8 +24,6 @@ export default function Home() {
|
||||
<a>Freundeskreis des VCP Rosenfeld</a>
|
||||
</footer>
|
||||
|
||||
<style jsx>{``}</style>
|
||||
|
||||
<style jsx global>{`
|
||||
html,
|
||||
body {
|
||||
@@ -37,6 +37,27 @@ export default function Home() {
|
||||
* {
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user