new-modal #4
@ -5,6 +5,7 @@ import { CalendarDate } from '@internationalized/date';
|
||||
import Layout from './Layout';
|
||||
import { RoomBooking } from './pages/RoomBooking';
|
||||
import { NewBooking } from './pages/NewBooking';
|
||||
import { BookingDetails } from './pages/BookingDetails';
|
||||
import { BookingSettings } from './pages/BookingSettings';
|
||||
import { CourseSchedule } from './pages/CourseSchedule';
|
||||
import { CourseScheduleView } from './pages/CourseScheduleView';
|
||||
@ -119,6 +120,7 @@ const AppRoutes = () => {
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<RoomBooking bookings={bookings} showSuccessBanner={showSuccessBanner} lastCreatedBooking={lastCreatedBooking} onDismissBanner={() => setShowSuccessBanner(false)} onBookingUpdate={updateBooking} onBookingDelete={deleteBooking} showDeleteBanner={showDeleteBanner} lastDeletedBooking={lastDeletedBooking} onDismissDeleteBanner={() => setShowDeleteBanner(false)} />} />
|
||||
<Route path="new-booking" element={<NewBooking addBooking={addBooking} />} />
|
||||
<Route path="booking-details" element={<BookingDetails addBooking={addBooking} />} />
|
||||
<Route path="course-schedule" element={<CourseSchedule />} />
|
||||
<Route path="course-schedule/:courseId" element={<CourseScheduleView />} />
|
||||
<Route path="booking-settings" element={<BookingSettings />} />
|
||||
|
||||
@ -15,6 +15,7 @@ export function BookingModal({
|
||||
setEndTimeIndex,
|
||||
className,
|
||||
onClose,
|
||||
onNavigateToDetails,
|
||||
isOpen = true
|
||||
}) {
|
||||
const booking = useBookingContext();
|
||||
@ -24,7 +25,6 @@ export function BookingModal({
|
||||
const initialEndTimeIndex = booking.selectedBookingLength > 0 ? startTimeIndex + booking.selectedBookingLength :
|
||||
(hoursAvailable === 1 ? startTimeIndex + 1 : null); // Auto-select 30 min if that's all that's available
|
||||
const [selectedEndTimeIndex, setSelectedEndTimeIndex] = useState(null);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const hasInitialized = useRef(false);
|
||||
|
||||
// Store the original hours available to prevent it from changing when selections are made
|
||||
@ -99,16 +99,19 @@ export function BookingModal({
|
||||
return durationSlots * 0.5; // Each slot is 30 minutes
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
if (currentStep === 1 && hasSelectedEndTime) {
|
||||
setCurrentStep(2);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackStep = () => {
|
||||
if (currentStep === 2) {
|
||||
setCurrentStep(1);
|
||||
const handleNavigateToDetails = () => {
|
||||
console.log('handleNavigateToDetails called', { hasSelectedEndTime, onNavigateToDetails });
|
||||
|
||||
onNavigateToDetails();
|
||||
/*
|
||||
if (hasSelectedEndTime) {
|
||||
// Close modal first, then navigate
|
||||
onClose && onClose();
|
||||
setTimeout(() => {
|
||||
onNavigateToDetails && onNavigateToDetails();
|
||||
}, 100);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
@ -122,69 +125,46 @@ export function BookingModal({
|
||||
>
|
||||
<Dialog style={{overflow: 'visible'}}>
|
||||
<form>
|
||||
{currentStep === 1 ? (
|
||||
<>
|
||||
<Heading slot="title">Välj sluttid</Heading>
|
||||
<p>{convertDateObjectToString(booking.selectedDate)}</p>
|
||||
<div className={styles.timeDisplay}>
|
||||
<div className={styles.timeRange}>
|
||||
<div className={styles.startTimeSection}>
|
||||
<label>Starttid</label>
|
||||
<div className={styles.startTimeValue}>{getTimeFromIndex(startTimeIndex)}</div>
|
||||
</div>
|
||||
<div className={styles.endTimeSection}>
|
||||
<label>Sluttid</label>
|
||||
<Dropdown
|
||||
options={endTimeOptions}
|
||||
disabledOptions={disabledOptions}
|
||||
onChange={handleChange}
|
||||
value={selectedEndTimeIndex || ""}
|
||||
placeholder={!initialEndTimeIndex ? {
|
||||
value: "",
|
||||
label: "Välj sluttid"
|
||||
} : null}
|
||||
className={styles.endTimeDropdown}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.modalContent}>
|
||||
<Heading slot="title">Välj sluttid</Heading>
|
||||
<p>{convertDateObjectToString(booking.selectedDate)}</p>
|
||||
<div className={styles.timeDisplay}>
|
||||
<div className={styles.timeRange}>
|
||||
<div className={styles.startTimeSection}>
|
||||
<label>Starttid</label>
|
||||
<div className={styles.startTimeValue}>{getTimeFromIndex(startTimeIndex)}</div>
|
||||
</div>
|
||||
<div className={styles.endTimeSection}>
|
||||
<label>Sluttid</label>
|
||||
<Dropdown
|
||||
options={endTimeOptions}
|
||||
disabledOptions={disabledOptions}
|
||||
onChange={handleChange}
|
||||
value={selectedEndTimeIndex || ""}
|
||||
placeholder={!initialEndTimeIndex ? {
|
||||
value: "",
|
||||
label: "Välj sluttid"
|
||||
} : null}
|
||||
className={styles.endTimeDropdown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.modalFooter}>
|
||||
<Button className={styles.cancelButton} slot="close">
|
||||
Avbryt
|
||||
</Button>
|
||||
<Button
|
||||
className={`${styles.saveButton} ${!hasSelectedEndTime ? styles.disabledButton : ''}`}
|
||||
onClick={hasSelectedEndTime ? handleNextStep : undefined}
|
||||
isDisabled={!hasSelectedEndTime}
|
||||
>
|
||||
{hasSelectedEndTime ? 'Nästa' : 'Välj sluttid först'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Heading slot="title">Bokningsuppgifter</Heading>
|
||||
<p>{convertDateObjectToString(booking.selectedDate)} · {getTimeFromIndex(startTimeIndex)} - {getTimeFromIndex(selectedEndTimeIndex)}</p>
|
||||
<BookingTitleField />
|
||||
<ParticipantsSelector />
|
||||
|
||||
<div className={styles.modalFooter}>
|
||||
<Button
|
||||
className={styles.cancelButton}
|
||||
onClick={handleBackStep}
|
||||
>
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button
|
||||
className={styles.saveButton}
|
||||
onClick={booking.handleSave}
|
||||
>
|
||||
Boka
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={styles.modalFooter}>
|
||||
<Button className={styles.cancelButton} slot="close">
|
||||
Avbryt
|
||||
</Button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.saveButton} ${!hasSelectedEndTime ? styles.disabledButton : ''}`}
|
||||
onClick={hasSelectedEndTime ? handleNavigateToDetails : undefined}
|
||||
disabled={!hasSelectedEndTime}
|
||||
>
|
||||
{hasSelectedEndTime ? 'Nästa' : 'Välj sluttid först'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
||||
@ -311,4 +311,44 @@
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Consistent modal sizing */
|
||||
:global(.react-aria-ModalOverlay .react-aria-Modal.react-aria-Modal) {
|
||||
height: 550px !important;
|
||||
width: 400px !important;
|
||||
max-width: 90vw !important;
|
||||
min-height: 550px !important;
|
||||
max-height: 550px !important;
|
||||
}
|
||||
|
||||
:global(.react-aria-ModalOverlay .react-aria-Modal.react-aria-Modal form) {
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
height: 450px;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modalFooter {
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
color: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-light);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@ -122,7 +122,7 @@
|
||||
border: 1px solid var(--dropdown-border);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
z-index: 1200;
|
||||
z-index: 1000;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-top: 0rem;
|
||||
@ -310,4 +310,5 @@
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TimeCard from './TimeCard';
|
||||
import { InlineBookingForm } from '../booking/InlineBookingForm';
|
||||
import { BookingModal } from '../booking/BookingModal';
|
||||
@ -10,11 +11,26 @@ import { useSettingsContext } from '../../context/SettingsContext';
|
||||
const SLOT_GROUPING_SIZE = 8;
|
||||
|
||||
export function TimeCardContainer() {
|
||||
const navigate = useNavigate();
|
||||
const booking = useBookingContext();
|
||||
const { settings } = useSettingsContext();
|
||||
|
||||
// Check if we should use inline form
|
||||
const useInlineForm = settings.bookingFormType === 'inline';
|
||||
|
||||
const handleNavigateToDetails = () => {
|
||||
console.log('TimeCardContainer handleNavigateToDetails called, navigating to /booking-details');
|
||||
navigate('/booking-details', {
|
||||
state: {
|
||||
selectedDate: booking.selectedDate,
|
||||
selectedStartIndex: booking.selectedStartIndex,
|
||||
selectedEndIndex: booking.selectedEndIndex,
|
||||
assignedRoom: booking.assignedRoom,
|
||||
title: booking.title,
|
||||
participants: booking.participants
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const slotCount = 24; // 12 hours * 2 slots per hour (8:00 to 20:00)
|
||||
const slotIndices = Array.from({ length: slotCount }, (_, i) => i);
|
||||
@ -160,6 +176,7 @@ export function TimeCardContainer() {
|
||||
setEndTimeIndex={booking.setSelectedEndIndex}
|
||||
className={modalStyles.modalContainer}
|
||||
onClose={() => booking.resetTimeSelections()}
|
||||
onNavigateToDetails={handleNavigateToDetails}
|
||||
isOpen={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -15,27 +15,36 @@ export function getTimeFromIndex(timeIndex) {
|
||||
}
|
||||
|
||||
export function convertDateObjectToString( date ) {
|
||||
const dayIndex = getDayOfWeek(date, "en-US");
|
||||
const monthIndex = date.month;
|
||||
|
||||
// Always use long format for now
|
||||
const isSmallScreen = false;
|
||||
|
||||
if (isSmallScreen) {
|
||||
const days = ["Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"];
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"];
|
||||
try {
|
||||
const dayIndex = getDayOfWeek(date, "en-US");
|
||||
const monthIndex = date.month;
|
||||
|
||||
const dayOfWeek = (dayIndex >= 0 && dayIndex < 7) ? days[dayIndex === 0 ? 6 : dayIndex - 1] : "Ogiltig dag";
|
||||
const monthName = (monthIndex >= 1 && monthIndex <= 12) ? months[monthIndex - 1] : "Ogiltig månad";
|
||||
// Always use long format for now
|
||||
const isSmallScreen = false;
|
||||
|
||||
return `${dayOfWeek} ${date.day} ${monthName}`;
|
||||
} else {
|
||||
const days = ["Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag"];
|
||||
const months = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];
|
||||
|
||||
const dayOfWeek = (dayIndex >= 0 && dayIndex < 7) ? days[dayIndex === 0 ? 6 : dayIndex - 1] : "Ogiltig dag";
|
||||
const monthName = (monthIndex >= 1 && monthIndex <= 12) ? months[monthIndex - 1] : "Ogiltig månad";
|
||||
|
||||
return `${dayOfWeek} ${date.day} ${monthName} ${date.year}`;
|
||||
if (isSmallScreen) {
|
||||
const days = ["Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"];
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"];
|
||||
|
||||
const dayOfWeek = (dayIndex >= 0 && dayIndex < 7) ? days[dayIndex === 0 ? 6 : dayIndex - 1] : "Ogiltig dag";
|
||||
const monthName = (monthIndex >= 1 && monthIndex <= 12) ? months[monthIndex - 1] : "Ogiltig månad";
|
||||
|
||||
return `${dayOfWeek} ${date.day} ${monthName}`;
|
||||
} else {
|
||||
const days = ["Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag"];
|
||||
const months = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];
|
||||
|
||||
const dayOfWeek = (dayIndex >= 0 && dayIndex < 7) ? days[dayIndex === 0 ? 6 : dayIndex - 1] : "Ogiltig dag";
|
||||
const monthName = (monthIndex >= 1 && monthIndex <= 12) ? months[monthIndex - 1] : "Ogiltig månad";
|
||||
|
||||
return `${dayOfWeek} ${date.day} ${monthName} ${date.year}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error converting date to string:', error);
|
||||
// Fallback to a simple format if the date conversion fails
|
||||
if (date && typeof date === 'object' && date.day && date.month && date.year) {
|
||||
return `${date.day}/${date.month}/${date.year}`;
|
||||
}
|
||||
return 'Ogiltigt datum';
|
||||
}
|
||||
}
|
||||
@ -187,6 +187,10 @@ export function useBookingState(addBooking, initialDate = null) {
|
||||
// Setters
|
||||
setTitle,
|
||||
setSelectedEndIndex,
|
||||
setSelectedDate,
|
||||
setSelectedStartIndex,
|
||||
setAssignedRoom,
|
||||
setParticipants,
|
||||
|
||||
// Handlers
|
||||
handleTimeCardClick,
|
||||
@ -222,5 +226,9 @@ export function useBookingState(addBooking, initialDate = null) {
|
||||
handleParticipantChange,
|
||||
handleRemoveParticipant,
|
||||
resetTimeSelections,
|
||||
setSelectedDate,
|
||||
setSelectedStartIndex,
|
||||
setAssignedRoom,
|
||||
setParticipants,
|
||||
]);
|
||||
}
|
||||
83
my-app/src/pages/BookingDetails.jsx
Normal file
83
my-app/src/pages/BookingDetails.jsx
Normal file
@ -0,0 +1,83 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import styles from './BookingDetails.module.css';
|
||||
import { BookingTitleField } from '../components/forms/BookingTitleField';
|
||||
import { ParticipantsSelector } from '../components/forms/ParticipantsSelector';
|
||||
import { useBookingContext } from '../context/BookingContext';
|
||||
import { BookingProvider } from '../context/BookingContext';
|
||||
import { useSettingsContext } from '../context/SettingsContext';
|
||||
import { useBookingState } from '../hooks/useBookingState';
|
||||
import { convertDateObjectToString, getTimeFromIndex } from '../helpers';
|
||||
|
||||
export function BookingDetails({ addBooking }) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { getEffectiveToday } = useSettingsContext();
|
||||
const booking = useBookingState(addBooking, getEffectiveToday());
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
// Populate booking state from navigation state if available
|
||||
useEffect(() => {
|
||||
const navigationState = location.state;
|
||||
|
||||
if (navigationState) {
|
||||
// Update booking state with navigation data
|
||||
if (navigationState.selectedDate) booking.setSelectedDate(navigationState.selectedDate);
|
||||
if (navigationState.selectedStartIndex !== undefined) booking.setSelectedStartIndex(navigationState.selectedStartIndex);
|
||||
if (navigationState.selectedEndIndex !== undefined) booking.setSelectedEndIndex(navigationState.selectedEndIndex);
|
||||
if (navigationState.assignedRoom) booking.setAssignedRoom(navigationState.assignedRoom);
|
||||
if (navigationState.title) booking.setTitle(navigationState.title);
|
||||
if (navigationState.participants) booking.setParticipants(navigationState.participants);
|
||||
} else if (!booking.selectedDate || !booking.selectedStartIndex || !booking.selectedEndIndex) {
|
||||
// Redirect back if no booking data from navigation or state
|
||||
navigate('/new-booking');
|
||||
}
|
||||
}, [location.state, navigate]);
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
booking.handleSave();
|
||||
};
|
||||
|
||||
return (
|
||||
<BookingProvider value={booking}>
|
||||
<div className={styles.pageContainer}>
|
||||
<div className={styles.header}>
|
||||
<button
|
||||
className={styles.backButton}
|
||||
onClick={handleBack}
|
||||
>
|
||||
← Tillbaka
|
||||
</button>
|
||||
<h2>Bokningsuppgifter</h2>
|
||||
</div>
|
||||
|
||||
<div className={styles.timeInfo}>
|
||||
<p className={styles.dateTime}>
|
||||
{convertDateObjectToString(booking.selectedDate)} · {getTimeFromIndex(booking.selectedStartIndex)} - {getTimeFromIndex(booking.selectedEndIndex)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.formContainer}>
|
||||
<BookingTitleField />
|
||||
<ParticipantsSelector />
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<button
|
||||
className={styles.saveButton}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Boka
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</BookingProvider>
|
||||
);
|
||||
}
|
||||
91
my-app/src/pages/BookingDetails.module.css
Normal file
91
my-app/src/pages/BookingDetails.module.css
Normal file
@ -0,0 +1,91 @@
|
||||
.pageContainer {
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
min-height: calc(100vh - 4rem); /* Adjust for header/footer height */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.backButton {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.backButton:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.timeInfo {
|
||||
background: var(--bg-secondary);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.dateTime {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.formContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--border-light);
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.saveButton {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.saveButton:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.saveButton:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.saveButton:focus {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user