improving-week-36 #1
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Button, Dialog, Heading, Modal } from 'react-aria-components';
|
||||
import { convertDateObjectToString, getTimeFromIndex } from '../helpers';
|
||||
import Dropdown from './Dropdown';
|
||||
@@ -13,6 +13,24 @@ export function BookingModal({
|
||||
className
|
||||
}) {
|
||||
const booking = useBookingContext();
|
||||
|
||||
// Initialize with pre-selected booking length if available
|
||||
const initialLength = booking.selectedBookingLength > 0 ? booking.selectedBookingLength : null;
|
||||
const [selectedLength, setSelectedLength] = useState(null);
|
||||
const [calculatedEndTime, setCalculatedEndTime] = useState(startTimeIndex);
|
||||
const hasInitialized = useRef(false);
|
||||
|
||||
// Effect to handle initial setup only once when modal opens
|
||||
useEffect(() => {
|
||||
if (initialLength && !hasInitialized.current) {
|
||||
setSelectedLength(initialLength);
|
||||
const newEndTime = startTimeIndex + initialLength;
|
||||
setCalculatedEndTime(newEndTime);
|
||||
setEndTimeIndex(newEndTime);
|
||||
booking.setSelectedEndIndex(newEndTime);
|
||||
hasInitialized.current = true;
|
||||
}
|
||||
}, [initialLength, startTimeIndex, setEndTimeIndex, booking]);
|
||||
|
||||
const bookingLengths = [
|
||||
{ value: 1, label: "30 min" },
|
||||
@@ -41,18 +59,51 @@ export function BookingModal({
|
||||
};
|
||||
|
||||
function handleChange(event) {
|
||||
const lengthValue = event.target.value === "" ? null : parseInt(event.target.value);
|
||||
console.log(event.target.value);
|
||||
setEndTimeIndex(startTimeIndex + parseInt(event.target.value));
|
||||
booking.setSelectedEndIndex(startTimeIndex + parseInt(event.target.value));
|
||||
setSelectedLength(lengthValue);
|
||||
|
||||
if (lengthValue !== null) {
|
||||
const newEndTime = startTimeIndex + lengthValue;
|
||||
setCalculatedEndTime(newEndTime);
|
||||
setEndTimeIndex(newEndTime);
|
||||
booking.setSelectedEndIndex(newEndTime);
|
||||
} else {
|
||||
// Reset to default state when placeholder is selected
|
||||
setCalculatedEndTime(startTimeIndex);
|
||||
setEndTimeIndex(startTimeIndex);
|
||||
booking.setSelectedEndIndex(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user has selected a booking length (including pre-selected)
|
||||
const hasSelectedLength = selectedLength !== null;
|
||||
|
||||
// Display time range - show calculated end time if length is selected
|
||||
const displayEndTime = hasSelectedLength ? calculatedEndTime : startTimeIndex;
|
||||
|
||||
|
||||
return (
|
||||
<Modal isDismissable className={className}>
|
||||
<Dialog>
|
||||
<form>
|
||||
<Heading slot="title">{booking.title == "" ? "Jacobs bokning" : booking.title}</Heading>
|
||||
<p>{convertDateObjectToString(booking.selectedDate)}</p>
|
||||
<p className={styles.timeSpan}>{getTimeFromIndex(startTimeIndex)} - {getTimeFromIndex(endTimeIndex)}</p>
|
||||
<div className={styles.timeDisplay}>
|
||||
<div className={styles.timeRange}>
|
||||
<div className={styles.startTime}>
|
||||
<label>Starttid</label>
|
||||
<span className={styles.timeValue}>{getTimeFromIndex(startTimeIndex)}</span>
|
||||
</div>
|
||||
<div className={styles.timeSeparator}>–</div>
|
||||
<div className={styles.endTime}>
|
||||
<label>Sluttid</label>
|
||||
<span className={`${styles.timeValue} ${!hasSelectedLength ? styles.placeholder : ''}`}>
|
||||
{hasSelectedLength ? getTimeFromIndex(displayEndTime) : "Välj längd"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.sectionWithTitle}>
|
||||
<label>Längd</label>
|
||||
@@ -60,7 +111,11 @@ export function BookingModal({
|
||||
options={bookingLengths}
|
||||
disabledOptions={disabledOptions}
|
||||
onChange={handleChange}
|
||||
placeholder={{ value: hoursAvailable, label: getLabelFromAvailableHours(hoursAvailable) }}
|
||||
value={selectedLength || ""}
|
||||
placeholder={!initialLength ? {
|
||||
value: "",
|
||||
label: "Välj bokningslängd"
|
||||
} : null}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -77,8 +132,12 @@ export function BookingModal({
|
||||
<Button className={styles.cancelButton} slot="close">
|
||||
Avbryt
|
||||
</Button>
|
||||
<Button className={styles.saveButton} onClick={booking.handleSave}>
|
||||
Boka
|
||||
<Button
|
||||
className={`${styles.saveButton} ${!hasSelectedLength ? styles.disabledButton : ''}`}
|
||||
onClick={hasSelectedLength ? booking.handleSave : undefined}
|
||||
isDisabled={!hasSelectedLength}
|
||||
>
|
||||
{hasSelectedLength ? 'Boka' : 'Välj längd först'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -17,7 +17,9 @@ export default function TimeCard({
|
||||
const booking = useBookingContext();
|
||||
|
||||
let hoursText;
|
||||
const halfHours = hoursAvailable;
|
||||
// Use the pre-selected booking length if available, otherwise use available hours
|
||||
const displayHours = booking.selectedBookingLength > 0 ? booking.selectedBookingLength : hoursAvailable;
|
||||
const halfHours = displayHours;
|
||||
|
||||
const [endTimeIndex, setEndTimeIndex] = useState(startTimeIndex + hoursAvailable);
|
||||
|
||||
@@ -51,7 +53,9 @@ export default function TimeCard({
|
||||
<Button
|
||||
className={`${classNames} ${className}`}
|
||||
onClick={() => {
|
||||
state === "availableSlot" ? handleClick : undefined;
|
||||
if (state === "availableSlot") {
|
||||
handleClick();
|
||||
}
|
||||
console.log("state: ", state);
|
||||
}}
|
||||
onMouseEnter={() => handleTimeCardHover(startTimeIndex)}
|
||||
@@ -60,7 +64,7 @@ export default function TimeCard({
|
||||
{(!isEndState && hoursAvailable > 0) || isEndState || state=="availableSlot" ? (
|
||||
<>
|
||||
<p className={styles.startTime}>{formatSlotIndex(startTimeIndex)}</p>
|
||||
<p className={styles.upToText}>{hoursAvailable > 1 && "Upp till "}<span className={styles.hoursText}>{hoursText}</span></p>
|
||||
<p className={styles.upToText}>{hoursAvailable > 1 && booking.selectedBookingLength === 0 && "Upp till "}<span className={styles.hoursText}>{hoursText}</span></p>
|
||||
</>
|
||||
) : null}
|
||||
</Button>
|
||||
|
||||
@@ -123,4 +123,72 @@
|
||||
background-color: white;
|
||||
width: 85%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* New time display styles */
|
||||
.timeDisplay {
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.timeRange {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.startTime, .endTime {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.startTime label, .endTime label {
|
||||
font-size: 0.75rem;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.timeValue {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.timeValue.placeholder {
|
||||
color: #adb5bd;
|
||||
font-style: italic;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.timeSeparator {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
color: #6c757d;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
/* Disabled button styles */
|
||||
.disabledButton {
|
||||
background-color: #e9ecef !important;
|
||||
color: #6c757d !important;
|
||||
border-color: #dee2e6 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.disabledButton:hover {
|
||||
background-color: #e9ecef !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.disabledButton:active {
|
||||
background-color: #e9ecef !important;
|
||||
}
|
||||
@@ -74,8 +74,22 @@ export function TimeCardContainer() {
|
||||
|
||||
/* Set time card state here: */
|
||||
let timeCardState = "unavailableSlot";
|
||||
if (maxConsecutive > 0) {
|
||||
timeCardState = "availableSlot";
|
||||
|
||||
// If a booking length is pre-selected, only show slots that can accommodate the exact length
|
||||
if (booking.selectedBookingLength !== 0) {
|
||||
// Check if this slot can accommodate the selected booking length
|
||||
const actualConsecutive = booking.currentRoom ?
|
||||
countConsecutiveFromSlot(booking.currentRoom.times, index) :
|
||||
Math.max(...booking.timeSlotsByRoom.map(room => countConsecutiveFromSlot(room.times, index)));
|
||||
|
||||
if (actualConsecutive >= booking.selectedBookingLength) {
|
||||
timeCardState = "availableSlot";
|
||||
}
|
||||
} else {
|
||||
// No pre-selected length, show if any time is available
|
||||
if (maxConsecutive > 0) {
|
||||
timeCardState = "availableSlot";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -58,6 +58,7 @@ export function useBookingState(addBooking, initialDate = null) {
|
||||
}, []);
|
||||
|
||||
const handleTimeCardClick = useCallback((startHour, hoursAvailable, roomId) => {
|
||||
console.log('TimeCard clicked:', { startHour, hoursAvailable, roomId });
|
||||
setSelectedStartIndex(startHour);
|
||||
setSelectedEndIndex(startHour + hoursAvailable);
|
||||
setSelectedRoom(roomId);
|
||||
@@ -92,6 +93,13 @@ export function useBookingState(addBooking, initialDate = null) {
|
||||
}, [resetTimeSelections]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
console.log('Saving booking with:', {
|
||||
selectedStartIndex,
|
||||
selectedEndIndex,
|
||||
selectedRoom,
|
||||
title
|
||||
});
|
||||
|
||||
addBooking({
|
||||
id: generateId(),
|
||||
date: selectedDate,
|
||||
|
||||
Reference in New Issue
Block a user