Merge pull request #163 from 50bbx/feature/add-timezone-select-in-calendar-view

Add timezone select in calendar view
pull/166/head
Bailey Pumfleet 2021-05-06 21:26:44 +01:00 committed by GitHub
commit 60d7351eeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 54 deletions

View File

@ -23,7 +23,7 @@ const getSlots = ({
if(!selectedDate) return [] if(!selectedDate) return []
const lowerBound = selectedDate.startOf("day"); const lowerBound = selectedDate.tz(selectedTimeZone).startOf("day");
// Simple case, same timezone // Simple case, same timezone
if (calendarTimeZone === selectedTimeZone) { if (calendarTimeZone === selectedTimeZone) {
@ -42,7 +42,7 @@ const getSlots = ({
return slots; return slots;
} }
const upperBound = selectedDate.endOf("day"); const upperBound = selectedDate.tz(selectedTimeZone).endOf("day");
// We need to start generating slots from the start of the calendarTimeZone day // We need to start generating slots from the start of the calendarTimeZone day
const startDateTime = lowerBound const startDateTime = lowerBound

View File

@ -23,7 +23,7 @@
"next-transpile-modules": "^7.0.0", "next-transpile-modules": "^7.0.0",
"react": "17.0.1", "react": "17.0.1",
"react-dom": "17.0.1", "react-dom": "17.0.1",
"react-timezone-select": "^0.10.10" "react-timezone-select": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^14.14.33", "@types/node": "^14.14.33",

View File

@ -5,6 +5,7 @@ import prisma from '../../lib/prisma';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
import TimezoneSelect from 'react-timezone-select';
import { ClockIcon, GlobeIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'; import { ClockIcon, GlobeIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isBetween from 'dayjs/plugin/isBetween'; import isBetween from 'dayjs/plugin/isBetween';
@ -32,6 +33,14 @@ export default function Type(props) {
const [busy, setBusy] = useState([]); const [busy, setBusy] = useState([]);
const telemetry = useTelemetry(); const telemetry = useTelemetry();
const [selectedTimeZone, setSelectedTimeZone] = useState('');
useEffect(() => {
// Setting timezone only client-side
setSelectedTimeZone(dayjs.tz.guess())
}, [])
// Get router variables // Get router variables
const router = useRouter(); const router = useRouter();
const { user } = router.query; const { user } = router.query;
@ -81,13 +90,13 @@ export default function Type(props) {
const calendar = [...emptyDays, ...days.map((day) => const calendar = [...emptyDays, ...days.map((day) =>
<button key={day} onClick={(e) => { <button key={day} onClick={(e) => {
telemetry.withJitsu((jitsu) => jitsu.track('date_selected', {page_title: "", source_ip: ""})) telemetry.withJitsu((jitsu) => jitsu.track('date_selected', {page_title: "", source_ip: ""}))
setSelectedDate(dayjs().tz(dayjs.tz.guess()).month(selectedMonth).date(day)) setSelectedDate(dayjs().tz(selectedTimeZone).month(selectedMonth).date(day))
}} disabled={selectedMonth < parseInt(dayjs().format('MM')) && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}> }} disabled={selectedMonth < parseInt(dayjs().format('MM')) && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}>
{day} {day}
</button> </button>
)]; )];
// Handle date change // Handle date change and timezone change
useEffect(() => { useEffect(() => {
const changeDate = async () => { const changeDate = async () => {
if (!selectedDate) { if (!selectedDate) {
@ -101,17 +110,18 @@ export default function Type(props) {
setLoading(false); setLoading(false);
} }
changeDate(); changeDate();
}, [selectedDate]); }, [selectedDate, selectedTimeZone]);
const times = useMemo(() =>
const times = getSlots({ getSlots({
calendarTimeZone: props.user.timeZone, calendarTimeZone: props.user.timeZone,
selectedTimeZone: dayjs.tz.guess(), selectedTimeZone: selectedTimeZone,
eventLength: props.eventType.length, eventLength: props.eventType.length,
selectedDate: selectedDate, selectedDate: selectedDate,
dayStartTime: props.user.startTime, dayStartTime: props.user.startTime,
dayEndTime: props.user.endTime, dayEndTime: props.user.endTime,
}) })
, [selectedDate, selectedTimeZone])
// Check for conflicts // Check for conflicts
for(let i = times.length - 1; i >= 0; i -= 1) { for(let i = times.length - 1; i >= 0; i -= 1) {
@ -135,7 +145,7 @@ export default function Type(props) {
const availableTimes = times.map((time) => const availableTimes = times.map((time) =>
<div key={dayjs(time).utc().format()}> <div key={dayjs(time).utc().format()}>
<Link href={`/${props.user.username}/book?date=${dayjs(time).utc().format()}&type=${props.eventType.id}`}> <Link href={`/${props.user.username}/book?date=${dayjs(time).utc().format()}&type=${props.eventType.id}`}>
<a key={dayjs(time).format("hh:mma")} className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">{dayjs(time).tz(dayjs.tz.guess()).format(is24h ? "HH:mm" : "hh:mma")}</a> <a key={dayjs(time).format("hh:mma")} className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">{dayjs(time).tz(selectedTimeZone).format(is24h ? "HH:mm" : "hh:mma")}</a>
</Link> </Link>
</div> </div>
); );
@ -158,39 +168,37 @@ export default function Type(props) {
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{props.eventType.length} minutes {props.eventType.length} minutes
</p> </p>
<button onClick={toggleTimeOptions} className="text-gray-500 mb-1 hover:bg-gray-100 rounded-full px-2 -ml-2 cursor-pointer"> <p className="text-gray-500 mb-1 px-2 py-1 -ml-2">
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1"/> <GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1"/>
{dayjs.tz.guess()} <ChevronDownIcon className="inline-block w-4 h-4 mb-1" /> <Switch.Group as="span" className="">
</button> <Switch.Label as="span" className="mr-3">
{ isTimeOptionsOpen && <span className="text-sm text-gray-500">am/pm</span>
<div className="bg-white rounded shadow p-4 absolute w-72"> </Switch.Label>
<Switch.Group as="div" className="flex items-center"> <Switch
<Switch.Label as="span" className="mr-3"> checked={is24h}
<span className="text-sm text-gray-500">am/pm</span> onChange={setIs24h}
</Switch.Label> className={classNames(
<Switch is24h ? 'bg-blue-600' : 'bg-gray-200',
checked={is24h} 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500'
onChange={setIs24h} )}
className={classNames( >
is24h ? 'bg-blue-600' : 'bg-gray-200', <span className="sr-only">Use setting</span>
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500' <span
)} aria-hidden="true"
> className={classNames(
<span className="sr-only">Use setting</span> is24h ? 'translate-x-5' : 'translate-x-0',
<span 'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200'
aria-hidden="true" )}
className={classNames( />
is24h ? 'translate-x-5' : 'translate-x-0', </Switch>
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200' <Switch.Label as="span" className="ml-3">
)} <span className="text-sm text-gray-500">24h</span>
/> </Switch.Label>
</Switch> </Switch.Group>
<Switch.Label as="span" className="ml-3"> </p>
<span className="text-sm text-gray-500">24h</span> <p className="mt-1 text-gray-500">
</Switch.Label> <TimezoneSelect id="timeZone" value={selectedTimeZone} onChange={({ value }) =>setSelectedTimeZone(value)} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" />
</Switch.Group> </p>
</div>
}
<p className="text-gray-600 mt-3 mb-8">{props.eventType.description}</p> <p className="text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
</div> </div>
<div className={"mt-8 sm:mt-0 " + (selectedDate ? 'sm:w-1/3 border-r sm:px-4' : 'sm:w-1/2 sm:pl-4')}> <div className={"mt-8 sm:mt-0 " + (selectedDate ? 'sm:w-1/3 border-r sm:px-4' : 'sm:w-1/2 sm:pl-4')}>
@ -268,4 +276,4 @@ export async function getServerSideProps(context) {
eventType eventType
}, },
} }
} }

View File

@ -2625,10 +2625,10 @@ react-select@^4.2.1:
react-input-autosize "^3.0.0" react-input-autosize "^3.0.0"
react-transition-group "^4.3.0" react-transition-group "^4.3.0"
react-timezone-select@^0.10.10: react-timezone-select@^1.0.2:
version "0.10.10" version "1.0.2"
resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-0.10.10.tgz#853aeb73e84fcf00bd01eb57c35f2df1b84e1cc0" resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-1.0.2.tgz#37e6d99bc15372bd3f9ebc7541bda6f3a10c852b"
integrity sha512-PEEQQkiL+fFW3940MmhrX6xNf2VMz16BW2UyF6Mu7jzCv89McwJ93Bp5mqE6ouhLPZSsyTnhjILifsEFUUMuFg== integrity sha512-MDv4rmDkop3nZcIH27tLwIuIVovJE6ushmr9r0kR1SSzVErdydV01vI1ch8u4JAAFpnR5lYDt5PBqQW/lUS+Jg==
dependencies: dependencies:
react-select "^4.2.1" react-select "^4.2.1"
spacetime "^6.14.0" spacetime "^6.14.0"