improving-week-36 #1

Merged
jare2473 merged 41 commits from improving-week-36 into main 2025-09-04 10:49:05 +02:00
5 changed files with 165 additions and 12 deletions
Showing only changes of commit 23080c0c08 - Show all commits

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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 (

View File

@@ -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,