improving-week-36 #1
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import AppRoutes from './AppRoutes'; // move the routing and loading logic here
|
||||
import { SettingsProvider } from './context/SettingsContext';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router basename={import.meta.env.BASE_URL}>
|
||||
<AppRoutes />
|
||||
</Router>
|
||||
<SettingsProvider>
|
||||
<Router basename={import.meta.env.BASE_URL}>
|
||||
<AppRoutes />
|
||||
</Router>
|
||||
</SettingsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import Layout from './Layout';
|
||||
import { RoomBooking } from './pages/RoomBooking';
|
||||
import { NewBooking } from './pages/NewBooking';
|
||||
import { CalendarSettings } from './pages/CalendarSettings';
|
||||
import FullScreenLoader from './components/FullScreenLoader';
|
||||
|
||||
const AppRoutes = () => {
|
||||
@@ -31,6 +32,7 @@ const AppRoutes = () => {
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<RoomBooking bookings={bookings} />} />
|
||||
<Route path="new-booking" element={<NewBooking addBooking={addBooking} />} />
|
||||
<Route path="calendar-settings" element={<CalendarSettings />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</>
|
||||
|
||||
@@ -3,11 +3,13 @@ import { DatePicker } from '../react-aria-starter/src/DatePicker';
|
||||
import { today, getLocalTimeZone } from '@internationalized/date';
|
||||
import { getFutureDate, isDateUnavailable } from '../utils/bookingUtils';
|
||||
import { useBookingContext } from '../context/BookingContext';
|
||||
import { useSettingsContext } from '../context/SettingsContext';
|
||||
|
||||
export function BookingDatePicker() {
|
||||
const booking = useBookingContext();
|
||||
const minDate = today(getLocalTimeZone());
|
||||
const maxDate = getFutureDate(14);
|
||||
const { settings, getEffectiveToday } = useSettingsContext();
|
||||
const minDate = getEffectiveToday();
|
||||
const maxDate = minDate.add({ days: settings.bookingRangeDays });
|
||||
|
||||
const handlePreviousDay = () => {
|
||||
const previousDay = booking.selectedDate.subtract({ days: 1 });
|
||||
@@ -34,7 +36,7 @@ export function BookingDatePicker() {
|
||||
firstDayOfWeek="mon"
|
||||
minValue={minDate}
|
||||
maxValue={maxDate}
|
||||
isDateUnavailable={isDateUnavailable}
|
||||
isDateUnavailable={(date) => isDateUnavailable(date, minDate, settings.bookingRangeDays)}
|
||||
onPreviousClick={handlePreviousDay}
|
||||
onNextClick={handleNextDay}
|
||||
canNavigatePrevious={canNavigatePrevious}
|
||||
|
||||
@@ -31,6 +31,7 @@ const Header = () => {
|
||||
<div className={styles.menu}>
|
||||
{/* Menu items */}
|
||||
<Link onClick={handleClick} to="/">Lokalbokning</Link>
|
||||
<Link onClick={handleClick} to="/calendar-settings">Calendar Settings</Link>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
97
my-app/src/context/SettingsContext.jsx
Normal file
97
my-app/src/context/SettingsContext.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { today, getLocalTimeZone, CalendarDate } from '@internationalized/date';
|
||||
|
||||
const SettingsContext = createContext();
|
||||
|
||||
export const useSettingsContext = () => {
|
||||
const context = useContext(SettingsContext);
|
||||
if (!context) {
|
||||
throw new Error('useSettingsContext must be used within a SettingsProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const SettingsProvider = ({ children }) => {
|
||||
const [settings, setSettings] = useState(() => {
|
||||
// Load settings from localStorage or use defaults
|
||||
const saved = localStorage.getItem('calendarSettings');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
return {
|
||||
...parsed,
|
||||
// Convert date strings back to DateValue objects
|
||||
mockToday: parsed.mockToday ? new Date(parsed.mockToday) : null,
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse saved settings:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Use mock date if set, otherwise real today
|
||||
mockToday: null,
|
||||
// Days in the future users can book
|
||||
bookingRangeDays: 14,
|
||||
// Room availability percentage
|
||||
roomAvailabilityChance: 0.7,
|
||||
// Number of rooms
|
||||
numberOfRooms: 5,
|
||||
// Earliest booking time (in half-hour slots from 8:00)
|
||||
earliestTimeSlot: 0, // 8:00
|
||||
// Latest booking time
|
||||
latestTimeSlot: 22, // 19:00 (last slot ending at 19:30)
|
||||
};
|
||||
});
|
||||
|
||||
// Save settings to localStorage whenever they change
|
||||
useEffect(() => {
|
||||
const toSave = {
|
||||
...settings,
|
||||
// Convert Date objects to strings for JSON serialization
|
||||
mockToday: settings.mockToday ? settings.mockToday.toISOString() : null,
|
||||
};
|
||||
localStorage.setItem('calendarSettings', JSON.stringify(toSave));
|
||||
}, [settings]);
|
||||
|
||||
// Get the effective "today" date (mock or real)
|
||||
const getEffectiveToday = () => {
|
||||
if (settings.mockToday) {
|
||||
// Convert JavaScript Date to CalendarDate using the proper library function
|
||||
const mockDate = settings.mockToday;
|
||||
const year = mockDate.getFullYear();
|
||||
const month = mockDate.getMonth() + 1; // JS months are 0-indexed
|
||||
const day = mockDate.getDate();
|
||||
|
||||
return new CalendarDate(year, month, day);
|
||||
}
|
||||
return today(getLocalTimeZone());
|
||||
};
|
||||
|
||||
const updateSettings = (newSettings) => {
|
||||
setSettings(prev => ({ ...prev, ...newSettings }));
|
||||
};
|
||||
|
||||
const resetSettings = () => {
|
||||
setSettings({
|
||||
mockToday: null,
|
||||
bookingRangeDays: 14,
|
||||
roomAvailabilityChance: 0.7,
|
||||
numberOfRooms: 5,
|
||||
earliestTimeSlot: 0,
|
||||
latestTimeSlot: 22,
|
||||
});
|
||||
localStorage.removeItem('calendarSettings');
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider value={{
|
||||
settings,
|
||||
updateSettings,
|
||||
resetSettings,
|
||||
getEffectiveToday,
|
||||
}}>
|
||||
{children}
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { DEFAULT_BOOKING_TITLE, PEOPLE } from '../constants/bookingConstants';
|
||||
import { useDisabledOptions } from './useDisabledOptions';
|
||||
|
||||
export function useBookingState(addBooking) {
|
||||
export function useBookingState(addBooking, initialDate = null) {
|
||||
// State hooks - simplified back to useState for stability
|
||||
const [timeSlotsByRoom, setTimeSlotsByRoom] = useState(generateInitialRooms());
|
||||
const [currentRoom, setCurrentRoom] = useState(null);
|
||||
@@ -18,7 +18,7 @@ export function useBookingState(addBooking) {
|
||||
const [selectedEndIndex, setSelectedEndIndex] = useState(null);
|
||||
const [selectedBookingLength, setSelectedBookingLength] = useState(0);
|
||||
const [participant, setParticipant] = useState("Arjohn Emilsson");
|
||||
const [selectedDate, setSelectedDate] = useState(today(getLocalTimeZone()));
|
||||
const [selectedDate, setSelectedDate] = useState(initialDate || today(getLocalTimeZone()));
|
||||
const [availableTimeSlots, setAvailableTimeSlots] = useState([]);
|
||||
const [indeciesInHover, setIndeciesInHover] = useState([]);
|
||||
const [title, setTitle] = useState("");
|
||||
|
||||
206
my-app/src/pages/CalendarSettings.jsx
Normal file
206
my-app/src/pages/CalendarSettings.jsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSettingsContext } from '../context/SettingsContext';
|
||||
import styles from './CalendarSettings.module.css';
|
||||
|
||||
export function CalendarSettings() {
|
||||
const { settings, updateSettings, resetSettings, getEffectiveToday } = useSettingsContext();
|
||||
const [tempDate, setTempDate] = useState('');
|
||||
|
||||
const handleMockDateChange = (e) => {
|
||||
const dateValue = e.target.value;
|
||||
setTempDate(dateValue);
|
||||
if (dateValue) {
|
||||
updateSettings({ mockToday: new Date(dateValue) });
|
||||
} else {
|
||||
updateSettings({ mockToday: null });
|
||||
}
|
||||
};
|
||||
|
||||
const formatDateForInput = (date) => {
|
||||
if (!date) return '';
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const getTimeFromSlot = (slot) => {
|
||||
const totalMinutes = 8 * 60 + slot * 30;
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
return `${hours}:${minutes === 0 ? '00' : '30'}`;
|
||||
};
|
||||
|
||||
const effectiveToday = getEffectiveToday();
|
||||
const isUsingMockDate = settings.mockToday !== null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<h1>Calendar Settings</h1>
|
||||
<p>Configure calendar behavior for testing purposes</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.section}>
|
||||
<h2>Date Settings</h2>
|
||||
|
||||
<div className={styles.setting}>
|
||||
<label htmlFor="mockDate">
|
||||
<strong>Mock Today's Date</strong>
|
||||
<span className={styles.description}>
|
||||
Override the current date for testing. Leave empty to use real date.
|
||||
</span>
|
||||
</label>
|
||||
<div className={styles.dateGroup}>
|
||||
<input
|
||||
id="mockDate"
|
||||
type="date"
|
||||
value={settings.mockToday ? formatDateForInput(settings.mockToday) : ''}
|
||||
onChange={handleMockDateChange}
|
||||
className={styles.dateInput}
|
||||
/>
|
||||
{isUsingMockDate && (
|
||||
<button
|
||||
onClick={() => {
|
||||
updateSettings({ mockToday: null });
|
||||
setTempDate('');
|
||||
}}
|
||||
className={styles.clearButton}
|
||||
>
|
||||
Use Real Date
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.currentStatus}>
|
||||
Effective today: <strong>{effectiveToday.day}/{effectiveToday.month}/{effectiveToday.year}</strong>
|
||||
{isUsingMockDate && <span className={styles.mockLabel}> (MOCK)</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.setting}>
|
||||
<label htmlFor="bookingRange">
|
||||
<strong>Booking Range (Days)</strong>
|
||||
<span className={styles.description}>
|
||||
How many days in the future users can book
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="bookingRange"
|
||||
type="number"
|
||||
min="1"
|
||||
max="365"
|
||||
value={settings.bookingRangeDays}
|
||||
onChange={(e) => updateSettings({ bookingRangeDays: parseInt(e.target.value) })}
|
||||
className={styles.numberInput}
|
||||
/>
|
||||
<div className={styles.currentStatus}>
|
||||
Latest bookable date: <strong>{effectiveToday.add({ days: settings.bookingRangeDays }).day}/{effectiveToday.add({ days: settings.bookingRangeDays }).month}/{effectiveToday.add({ days: settings.bookingRangeDays }).year}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h2>Room Settings</h2>
|
||||
|
||||
<div className={styles.setting}>
|
||||
<label htmlFor="numberOfRooms">
|
||||
<strong>Number of Rooms</strong>
|
||||
<span className={styles.description}>
|
||||
Total number of rooms available for booking
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="numberOfRooms"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
value={settings.numberOfRooms}
|
||||
onChange={(e) => updateSettings({ numberOfRooms: parseInt(e.target.value) })}
|
||||
className={styles.numberInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.setting}>
|
||||
<label htmlFor="availabilityChance">
|
||||
<strong>Room Availability %</strong>
|
||||
<span className={styles.description}>
|
||||
Percentage chance that a time slot is available (affects random generation)
|
||||
</span>
|
||||
</label>
|
||||
<div className={styles.sliderGroup}>
|
||||
<input
|
||||
id="availabilityChance"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={settings.roomAvailabilityChance}
|
||||
onChange={(e) => updateSettings({ roomAvailabilityChance: parseFloat(e.target.value) })}
|
||||
className={styles.slider}
|
||||
/>
|
||||
<span className={styles.sliderValue}>
|
||||
{Math.round(settings.roomAvailabilityChance * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h2>Time Slot Settings</h2>
|
||||
|
||||
<div className={styles.setting}>
|
||||
<label htmlFor="earliestTime">
|
||||
<strong>Earliest Booking Time</strong>
|
||||
<span className={styles.description}>
|
||||
First available time slot of the day
|
||||
</span>
|
||||
</label>
|
||||
<select
|
||||
id="earliestTime"
|
||||
value={settings.earliestTimeSlot}
|
||||
onChange={(e) => updateSettings({ earliestTimeSlot: parseInt(e.target.value) })}
|
||||
className={styles.select}
|
||||
>
|
||||
{Array.from({ length: 23 }, (_, i) => (
|
||||
<option key={i} value={i}>
|
||||
{getTimeFromSlot(i)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className={styles.setting}>
|
||||
<label htmlFor="latestTime">
|
||||
<strong>Latest Booking Time</strong>
|
||||
<span className={styles.description}>
|
||||
Last available time slot of the day
|
||||
</span>
|
||||
</label>
|
||||
<select
|
||||
id="latestTime"
|
||||
value={settings.latestTimeSlot}
|
||||
onChange={(e) => updateSettings({ latestTimeSlot: parseInt(e.target.value) })}
|
||||
className={styles.select}
|
||||
>
|
||||
{Array.from({ length: 23 }, (_, i) => (
|
||||
<option key={i} value={i} disabled={i <= settings.earliestTimeSlot}>
|
||||
{getTimeFromSlot(i)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<button onClick={resetSettings} className={styles.resetButton}>
|
||||
Reset to Defaults
|
||||
</button>
|
||||
<div className={styles.info}>
|
||||
Settings are automatically saved and will persist between sessions
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
236
my-app/src/pages/CalendarSettings.module.css
Normal file
236
my-app/src/pages/CalendarSettings.module.css
Normal file
@@ -0,0 +1,236 @@
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #6b7280;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 2px solid #f3f4f6;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.setting {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.setting:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.setting label strong {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dateGroup {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dateInput, .numberInput, .select {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.dateInput:focus, .numberInput:focus, .select:focus {
|
||||
outline: none;
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.dateInput {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.numberInput {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.clearButton {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.clearButton:hover {
|
||||
background: #e5e7eb;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.currentStatus {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.mockLabel {
|
||||
background: #fbbf24;
|
||||
color: #92400e;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.sliderGroup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: #e5e7eb;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #2563eb;
|
||||
cursor: pointer;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #2563eb;
|
||||
cursor: pointer;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.sliderValue {
|
||||
font-weight: 600;
|
||||
color: #2563eb;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 2rem;
|
||||
background: #f9fafb;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.resetButton {
|
||||
padding: 0.75rem 2rem;
|
||||
background: #dc2626;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.resetButton:hover {
|
||||
background: #b91c1c;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dateGroup {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.sliderGroup {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@ import { RoomSelectionField } from '../components/RoomSelectionField';
|
||||
import { BookingLengthField } from '../components/BookingLengthField';
|
||||
import { useBookingState } from '../hooks/useBookingState';
|
||||
import { BookingProvider } from '../context/BookingContext';
|
||||
import { useSettingsContext } from '../context/SettingsContext';
|
||||
|
||||
export function NewBooking({ addBooking }) {
|
||||
const booking = useBookingState(addBooking);
|
||||
const { getEffectiveToday } = useSettingsContext();
|
||||
const booking = useBookingState(addBooking, getEffectiveToday());
|
||||
|
||||
return (
|
||||
<BookingProvider value={booking}>
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
|
||||
&[data-disabled] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.react-aria-CalendarCell {
|
||||
@@ -39,6 +43,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover:not([data-selected]):not([data-disabled]):not([data-unavailable]) {
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
&[data-pressed] {
|
||||
background: var(--gray-100);
|
||||
}
|
||||
@@ -64,6 +72,7 @@
|
||||
&[data-unavailable] {
|
||||
text-decoration: line-through;
|
||||
color: var(--invalid-color);
|
||||
color: rgb(203, 203, 203);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,8 @@
|
||||
|
||||
.react-aria-Popover[data-trigger=DatePicker] {
|
||||
max-width: unset;
|
||||
transform: translateX(-50%);
|
||||
left: 50% !important;
|
||||
}
|
||||
|
||||
.react-aria-DatePicker {
|
||||
|
||||
@@ -74,15 +74,14 @@ export const getLongestConsecutive = (allRooms) => {
|
||||
return longest;
|
||||
};
|
||||
|
||||
export const createDisabledRanges = () => {
|
||||
const now = today(getLocalTimeZone());
|
||||
export const createDisabledRanges = (effectiveToday, bookingRangeDays = 14) => {
|
||||
return [
|
||||
[now.add({ days: 14 }), now.add({ days: 9999 })],
|
||||
[effectiveToday.add({ days: bookingRangeDays }), effectiveToday.add({ days: 9999 })],
|
||||
];
|
||||
};
|
||||
|
||||
export const isDateUnavailable = (date) => {
|
||||
const disabledRanges = createDisabledRanges();
|
||||
export const isDateUnavailable = (date, effectiveToday, bookingRangeDays = 14) => {
|
||||
const disabledRanges = createDisabledRanges(effectiveToday, bookingRangeDays);
|
||||
return disabledRanges.some((interval) =>
|
||||
date.compare(interval[0]) >= 0 &&
|
||||
date.compare(interval[1]) <= 0
|
||||
|
||||
Reference in New Issue
Block a user