-
-
- {hasSelectedLength ? getTimeFromIndex(displayEndTime) : "Välj längd"}
-
+
+
Välj sluttid
+
{convertDateObjectToString(booking.selectedDate)}
+
+
+
+
+
{getTimeFromIndex(startTimeIndex)}
+
+
+
+
+
-
-
-
-
-
-
-
-
{booking.selectedRoom !== "allRooms" ? booking.selectedRoom : (booking.assignedRoom || 'Inget rum tilldelat')}
-
-
-
-
-
- {(() => {
- const currentUser = getCurrentUser();
- const allParticipants = [currentUser, ...booking.participants.filter(p => p.id !== currentUser.id)];
- return allParticipants.map(p => p.name).join(", ");
- })()}
-
-
-
+ {hasSelectedEndTime ? 'Nästa' : 'Välj sluttid först'}
+
diff --git a/my-app/src/components/booking/BookingModal.module.css b/my-app/src/components/booking/BookingModal.module.css
index 6f6e4b1..b45b2d0 100644
--- a/my-app/src/components/booking/BookingModal.module.css
+++ b/my-app/src/components/booking/BookingModal.module.css
@@ -139,54 +139,148 @@
/* New time display styles */
.timeDisplay {
margin: 1rem 0;
- padding: 1rem;
- background-color: var(--modal-display-bg);
- border-radius: 8px;
- border: 1px solid var(--modal-display-border);
min-width: 196px;
width: fit-content;
}
.timeRange {
display: flex;
- align-items: center;
- justify-content: space-between;
+ flex-direction: column;
gap: 1rem;
}
-.startTime, .endTime {
+.startTimeSection {
display: flex;
flex-direction: column;
- align-items: center;
+ width: fit-content;
}
-.startTime label, .endTime label {
- font-size: 0.75rem;
- color: var(--text-secondary);
- font-weight: 500;
- margin-bottom: 0.25rem;
- text-transform: uppercase;
- letter-spacing: 0.5px;
+.endTimeSection {
+ display: flex;
+ flex-direction: column;
+ width: fit-content;
}
-.timeValue {
- font-size: 1.5rem;
- font-weight: 600;
+.startTimeSection label, .endTimeSection label {
+ font-size: 0.8rem;
+ color: var(--text-tertiary);
+}
+
+.startTimeValue {
+ margin: 0;
+ font-size: 1.8rem;
+ font-weight: 400;
color: var(--text-primary);
}
-.timeValue.placeholder {
- color: var(--text-muted);
- font-style: italic;
+.timeSeparator {
+ font-size: 2.5rem;
+ font-weight: 300;
+ color: var(--text-secondary);
+ margin: 0 0.75rem;
+ padding-top: 1.5rem;
+}
+
+/* Custom End Time Dropdown */
+.customEndTimeDropdown {
+ position: relative;
+ min-width: fit-content;
+}
+
+.endTimeButton {
+ background: var(--input-bg);
+ border: 1px solid var(--input-border);
+ border-radius: 0.375rem;
+ padding: 0.75rem 2.5rem 0.75rem 1rem;
+ cursor: pointer;
+ font-size: 1.8rem;
+ text-align: center;
+ min-width: 200px;
+ position: relative;
+ transition: all 0.2s ease;
+}
+
+.endTimeButton:hover {
+ border-color: var(--color-primary);
+}
+
+.endTimeButton:focus {
+ outline: none;
+ border-color: var(--color-primary);
+ box-shadow: 0 0 0 2px var(--color-primary-light);
+}
+
+.endTimeButton::after {
+ content: '▼';
+ position: absolute;
+ right: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 0.8rem;
+ color: var(--dropdown-chevron-color);
+}
+
+.timeText {
+ font-weight: 700;
+ color: var(--text-primary);
+ font-feature-settings: 'tnum';
+}
+
+.durationText {
+ font-weight: 400;
+ color: var(--text-tertiary);
+ margin-left: 0.5rem;
+}
+
+.placeholderText {
+ font-weight: 400;
+ color: var(--text-secondary);
font-size: 1rem;
}
-.timeSeparator {
- font-size: 1.5rem;
+.endTimeOptionsDropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ background: var(--input-bg);
+ border: 1px solid var(--input-border);
+ border-radius: 0.375rem;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ z-index: 1000;
+ max-height: 200px;
+ overflow-y: auto;
+ margin-top: 4px;
+}
+
+.endTimeOption {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ background: none;
+ border: none;
+ padding: 0.75rem 1rem;
+ cursor: pointer;
+ text-align: left;
+ transition: background-color 0.2s ease;
+}
+
+.endTimeOption:hover {
+ background: var(--bg-secondary);
+}
+
+.optionTime {
+ font-weight: 700;
+ color: var(--text-primary);
+ font-feature-settings: 'tnum';
+ font-size: 1.1rem;
+}
+
+.optionDuration {
font-weight: 400;
- color: var(--text-secondary);
- margin: 0 0.5rem;
- padding-top: 1.3rem;
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
}
/* Disabled button styles */
@@ -210,4 +304,51 @@
.disabledButton:active {
background-color: var(--button-disabled-bg) !important;
transform: none !important;
+}
+
+.bookingForms {
+ display: flex;
+ 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;
}
\ No newline at end of file
diff --git a/my-app/src/components/booking/InlineBookingForm.module.css b/my-app/src/components/booking/InlineBookingForm.module.css
index 1f78cc8..15adace 100644
--- a/my-app/src/components/booking/InlineBookingForm.module.css
+++ b/my-app/src/components/booking/InlineBookingForm.module.css
@@ -1,10 +1,10 @@
.inlineForm {
- background: white;
- border: 1px solid #D1D5DB;
- border-radius: 0.5rem;
- padding: 1.5rem;
- margin: 1rem 0;
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+ background: var(--modal-bg);
+ border: 1px solid var(--border-light);
+ border-radius: var(--border-radius-lg);
+ padding: var(--spacing-2xl);
+ margin: var(--spacing-lg) 0;
+ box-shadow: var(--shadow-lg);
animation: slideDown 0.2s ease-out;
width: 100%;
flex-basis: 100%;
@@ -22,7 +22,7 @@
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
- border-bottom: 8px solid #D1D5DB;
+ border-bottom: 8px solid var(--border-light);
}
.arrowLeft::after {
@@ -34,7 +34,7 @@
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
- border-bottom: 7px solid white;
+ border-bottom: 7px solid var(--modal-bg);
}
/* Arrow pointing to right card */
@@ -47,7 +47,7 @@
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
- border-bottom: 8px solid #D1D5DB;
+ border-bottom: 8px solid var(--border-light);
}
.arrowRight::after {
@@ -59,18 +59,18 @@
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
- border-bottom: 7px solid white;
+ border-bottom: 7px solid var(--modal-bg);
}
.formHeader {
text-align: center;
- margin-bottom: 1rem;
- padding-bottom: 0.75rem;
- border-bottom: 1px solid #E5E7EB;
+ margin-bottom: var(--spacing-lg);
+ padding-bottom: var(--spacing-md);
+ border-bottom: 1px solid var(--border-light);
}
.section {
- margin-bottom: 1.5rem;
+ margin-bottom: var(--spacing-2xl);
}
.section:last-of-type {
@@ -78,86 +78,87 @@
}
.formHeader h3 {
- margin: 0 0 0.5rem 0;
- font-size: 1.25rem;
- font-weight: 700;
- color: #111827;
+ margin: 0 0 var(--spacing-sm) 0;
+ font-size: var(--font-size-3xl);
+ font-weight: var(--font-weight-bold);
+ color: var(--text-primary);
}
.dateText {
margin: 0;
- color: #6B7280;
- font-size: 0.875rem;
+ color: var(--text-secondary);
+ font-size: var(--font-size-sm);
}
.timeDisplay {
- margin-bottom: 1rem;
+ margin-bottom: var(--spacing-lg);
}
.timeRange {
display: flex;
align-items: center;
justify-content: center;
- gap: 1rem;
- padding: 1rem;
- background: #F9FAFB;
- border-radius: 0.375rem;
+ gap: var(--spacing-lg);
+ padding: var(--spacing-lg);
+ background: var(--modal-display-bg);
+ border: 1px solid var(--modal-display-border);
+ border-radius: var(--border-radius-md);
}
.timeItem {
display: flex;
flex-direction: column;
align-items: center;
- gap: 0.25rem;
+ gap: var(--spacing-xs);
}
.timeItem label {
- font-size: 0.75rem;
- color: #6B7280;
- font-weight: 500;
+ font-size: var(--font-size-xs);
+ color: var(--text-secondary);
+ font-weight: var(--font-weight-medium);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.timeValue {
- font-size: 1.25rem;
- font-weight: 600;
- color: #111827;
+ font-size: var(--font-size-3xl);
+ font-weight: var(--font-weight-semibold);
+ color: var(--text-primary);
}
.timeValue.placeholder {
- color: #9CA3AF;
+ color: var(--text-tertiary);
font-style: italic;
}
.timeSeparator {
- font-size: 1.5rem;
- color: #6B7280;
- font-weight: 300;
+ font-size: var(--font-size-4xl);
+ color: var(--text-secondary);
+ font-weight: var(--font-weight-light);
}
.formField {
- margin-bottom: 1rem;
+ margin-bottom: var(--spacing-lg);
}
.formField label {
display: block;
- font-size: 0.875rem;
- font-weight: 500;
- color: #374151;
- margin-bottom: 0.5rem;
+ font-size: var(--font-size-sm);
+ font-weight: var(--font-weight-medium);
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-sm);
}
.sectionWithTitle {
- padding-top: 1rem;
+ padding-top: var(--spacing-lg);
display: flex;
flex-direction: column;
width: fit-content;
}
.sectionWithTitle label {
- font-size: 0.8rem;
- color: #717171;
+ font-size: var(--font-size-xs);
+ color: var(--text-secondary);
}
.sectionWithTitle p {
@@ -166,77 +167,77 @@
.formActions {
display: flex;
- gap: 1rem;
- margin-top: 2rem;
- padding-top: 1.5rem;
- border-top: 1px solid #E5E7EB;
+ gap: var(--spacing-lg);
+ margin-top: var(--spacing-3xl);
+ padding-top: var(--spacing-2xl);
+ border-top: 1px solid var(--border-light);
}
.cancelButton {
flex: 1;
- background-color: white;
+ background-color: var(--modal-cancel-bg);
height: 2.75rem;
- color: #374151;
- font-weight: 600;
- border: 2px solid #d1d5db;
- border-radius: 0.375rem;
- transition: all 0.2s ease;
+ color: var(--modal-cancel-text);
+ font-weight: var(--font-weight-semibold);
+ border: 2px solid var(--modal-cancel-border);
+ border-radius: var(--border-radius-md);
+ transition: var(--transition-medium);
cursor: pointer;
- font-size: 0.875rem;
+ font-size: var(--font-size-sm);
}
.cancelButton:hover {
- background-color: #f9fafb;
- border-color: #9ca3af;
+ background-color: var(--modal-cancel-hover-bg);
+ border-color: var(--modal-cancel-hover-border);
}
.cancelButton:active {
- background-color: #e5e7eb;
+ background-color: var(--modal-cancel-active-bg);
transform: translateY(1px);
}
.saveButton {
flex: 2;
- background-color: #059669;
- color: white;
+ background-color: var(--modal-save-bg);
+ color: var(--modal-save-text);
height: 2.75rem;
- font-weight: 600;
- font-size: 0.875rem;
- border: 2px solid #047857;
- border-radius: 0.375rem;
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgba(5, 150, 105, 0.2);
+ font-weight: var(--font-weight-semibold);
+ font-size: var(--font-size-sm);
+ border: 2px solid var(--modal-save-border);
+ border-radius: var(--border-radius-md);
+ transition: var(--transition-medium);
+ box-shadow: var(--modal-save-shadow);
cursor: pointer;
}
.saveButton:hover {
- background-color: #047857;
- box-shadow: 0 4px 8px rgba(5, 150, 105, 0.3);
+ background-color: var(--modal-save-hover-bg);
+ box-shadow: var(--modal-save-hover-shadow);
}
.saveButton:active {
- background-color: #065f46;
+ background-color: var(--modal-save-active-bg);
transform: translateY(1px);
- box-shadow: 0 1px 2px rgba(5, 150, 105, 0.2);
+ box-shadow: var(--modal-save-active-shadow);
}
.disabledButton {
- background-color: #f8f9fa !important;
- color: #adb5bd !important;
- border: 2px dashed #dee2e6 !important;
+ background-color: var(--button-disabled-bg) !important;
+ color: var(--button-disabled-text) !important;
+ border: 2px dashed var(--button-disabled-border) !important;
opacity: 0.6 !important;
box-shadow: none !important;
cursor: default !important;
}
.disabledButton:hover {
- background-color: #f8f9fa !important;
+ background-color: var(--button-disabled-bg) !important;
transform: none !important;
box-shadow: none !important;
}
.disabledButton:active {
- background-color: #f8f9fa !important;
+ background-color: var(--button-disabled-bg) !important;
transform: none !important;
}
diff --git a/my-app/src/components/booking/InlineModalBookingForm.jsx b/my-app/src/components/booking/InlineModalBookingForm.jsx
new file mode 100644
index 0000000..d459a39
--- /dev/null
+++ b/my-app/src/components/booking/InlineModalBookingForm.jsx
@@ -0,0 +1,136 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Button } from 'react-aria-components';
+import { convertDateObjectToString, getTimeFromIndex } from '../../helpers';
+import Dropdown from '../ui/Dropdown';
+import { useBookingContext } from '../../context/BookingContext';
+import { useSettingsContext } from '../../context/SettingsContext';
+import styles from './InlineModalBookingForm.module.css';
+
+export function InlineModalBookingForm({
+ startTimeIndex,
+ hoursAvailable,
+ endTimeIndex,
+ setEndTimeIndex,
+ onClose,
+ onNavigateToDetails,
+ arrowPointsLeft = true
+}) {
+ const booking = useBookingContext();
+ const { getCurrentUser, getDefaultBookingTitle } = useSettingsContext();
+
+ // Initialize with pre-selected end time if available, or auto-select if only 30 min available
+ 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 hasInitialized = useRef(false);
+
+ // Store the original hours available to prevent it from changing when selections are made
+ const originalHoursAvailable = useRef(hoursAvailable);
+ if (originalHoursAvailable.current < hoursAvailable) {
+ originalHoursAvailable.current = hoursAvailable;
+ }
+
+ // Effect to handle initial setup only once when form opens
+ useEffect(() => {
+ if (initialEndTimeIndex && !hasInitialized.current) {
+ setSelectedEndTimeIndex(initialEndTimeIndex);
+ setEndTimeIndex(initialEndTimeIndex);
+ booking.setSelectedEndIndex(initialEndTimeIndex);
+ hasInitialized.current = true;
+ }
+ }, [initialEndTimeIndex, setEndTimeIndex, booking]);
+
+ // Generate end time options based on available hours
+ const endTimeOptions = [];
+ const disabledOptions = {};
+
+ // Always show all possible options up to the original available hours, not limited by current selection
+ const maxOptions = Math.min(originalHoursAvailable.current, 8);
+
+ for (let i = 1; i <= maxOptions; i++) {
+ const endTimeIndex = startTimeIndex + i;
+ const endTime = getTimeFromIndex(endTimeIndex);
+ const durationLabel = i === 1 ? "30 min" :
+ i === 2 ? "1 h" :
+ i === 3 ? "1.5 h" :
+ i === 4 ? "2 h" :
+ i === 5 ? "2.5 h" :
+ i === 6 ? "3 h" :
+ i === 7 ? "3.5 h" :
+ i === 8 ? "4 h" : `${i * 0.5} h`;
+
+ endTimeOptions.push({
+ value: endTimeIndex,
+ label: `${endTime} · ${durationLabel}`
+ });
+
+ disabledOptions[endTimeIndex] = false; // All available options are enabled
+ }
+
+ function handleChange(event) {
+ const endTimeValue = event.target.value === "" ? null : parseInt(event.target.value);
+ setSelectedEndTimeIndex(endTimeValue);
+
+ if (endTimeValue !== null) {
+ setEndTimeIndex(endTimeValue);
+ booking.setSelectedEndIndex(endTimeValue);
+ // Update the selected booking length in context so it doesn't interfere
+ const newLength = endTimeValue - startTimeIndex;
+ booking.setSelectedBookingLength && booking.setSelectedBookingLength(newLength);
+ } else {
+ // Reset to default state when placeholder is selected
+ setEndTimeIndex(startTimeIndex);
+ booking.setSelectedEndIndex(null);
+ booking.setSelectedBookingLength && booking.setSelectedBookingLength(0);
+ }
+ }
+
+ // Check if user has selected an end time (including pre-selected)
+ const hasSelectedEndTime = selectedEndTimeIndex !== null;
+
+ const handleNavigateToDetails = () => {
+ if (hasSelectedEndTime) {
+ onNavigateToDetails && onNavigateToDetails();
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+ {/*
Välj sluttid
*/}
+ {/* Time Selection */}
+
+
+
+ {/* Actions */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/my-app/src/components/booking/InlineModalBookingForm.module.css b/my-app/src/components/booking/InlineModalBookingForm.module.css
new file mode 100644
index 0000000..17294bc
--- /dev/null
+++ b/my-app/src/components/booking/InlineModalBookingForm.module.css
@@ -0,0 +1,192 @@
+.inlineForm {
+ background: var(--modal-bg);
+ border: 1px solid var(--border-light);
+ border-radius: var(--border-radius-lg);
+ padding: var(--spacing-2xl);
+ margin: var(--spacing-lg) 0;
+ box-shadow: var(--shadow-lg);
+ animation: slideDown 0.2s ease-out;
+ width: 100%;
+ flex-basis: 100%;
+ max-width: none;
+ position: relative;
+}
+
+/* Arrow pointing to left card */
+.arrowLeft::before {
+ content: '';
+ position: absolute;
+ top: -8px;
+ left: 75px;
+ width: 0;
+ height: 0;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-bottom: 8px solid var(--border-light);
+}
+
+.arrowLeft::after {
+ content: '';
+ position: absolute;
+ top: -7px;
+ left: 76px;
+ width: 0;
+ height: 0;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid var(--modal-bg);
+}
+
+/* Arrow pointing to right card */
+.arrowRight::before {
+ content: '';
+ position: absolute;
+ top: -8px;
+ right: 75px;
+ width: 0;
+ height: 0;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-bottom: 8px solid var(--border-light);
+}
+
+.arrowRight::after {
+ content: '';
+ position: absolute;
+ top: -7px;
+ right: 76px;
+ width: 0;
+ height: 0;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid var(--modal-bg);
+}
+
+.formHeader {
+ text-align: center;
+ /*margin-bottom: var(--spacing-2xl);*/
+ /*padding-bottom: var(--spacing-lg);*/
+ /*border-bottom: 1px solid var(--border-light);*/
+}
+
+.formTitle {
+ margin: 0 0 var(--spacing-sm) 0;
+ font-size: var(--font-size-3xl);
+ font-weight: var(--font-weight-bold);
+ color: var(--text-primary);
+}
+
+.dateText {
+ margin: 0;
+ color: var(--text-secondary);
+ font-size: var(--font-size-sm);
+}
+
+.section {
+ margin-bottom: var(--spacing-2xl);
+}
+
+.formField {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-sm);
+}
+
+.formField label {
+ font-size: var(--font-size-sm);
+ font-weight: var(--font-weight-medium);
+ color: var(--text-primary);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.endTimeDropdown {
+ width: 100%;
+}
+
+.formActions {
+ display: flex;
+ gap: var(--spacing-lg);
+ /*margin-top: var(--spacing-3xl);*/
+ /*padding-top: var(--spacing-2xl);*/
+ /*border-top: 1px solid var(--border-light);*/
+}
+
+.cancelButton {
+ flex: 1;
+ background-color: var(--modal-cancel-bg);
+ height: 2.75rem;
+ color: var(--modal-cancel-text);
+ font-weight: var(--font-weight-semibold);
+ border: 2px solid var(--modal-cancel-border);
+ border-radius: var(--border-radius-md);
+ transition: var(--transition-medium);
+ cursor: pointer;
+ font-size: var(--font-size-sm);
+}
+
+.cancelButton:hover {
+ background-color: var(--modal-cancel-hover-bg);
+ border-color: var(--modal-cancel-hover-border);
+}
+
+.cancelButton:active {
+ background-color: var(--modal-cancel-active-bg);
+ transform: translateY(1px);
+}
+
+.saveButton {
+ flex: 2;
+ background-color: var(--modal-save-bg);
+ color: var(--modal-save-text);
+ height: 2.75rem;
+ font-weight: var(--font-weight-semibold);
+ font-size: var(--font-size-sm);
+ border: 2px solid var(--modal-save-border);
+ border-radius: var(--border-radius-md);
+ transition: var(--transition-medium);
+ box-shadow: var(--modal-save-shadow);
+ cursor: pointer;
+}
+
+.saveButton:hover {
+ background-color: var(--modal-save-hover-bg);
+ box-shadow: var(--modal-save-hover-shadow);
+}
+
+.saveButton:active {
+ background-color: var(--modal-save-active-bg);
+ transform: translateY(1px);
+ box-shadow: var(--modal-save-active-shadow);
+}
+
+.disabledButton {
+ background-color: var(--button-disabled-bg) !important;
+ color: var(--button-disabled-text) !important;
+ border: 2px dashed var(--button-disabled-border) !important;
+ opacity: 0.6 !important;
+ box-shadow: none !important;
+ cursor: default !important;
+}
+
+.disabledButton:hover {
+ background-color: var(--button-disabled-bg) !important;
+ transform: none !important;
+ box-shadow: none !important;
+}
+
+.disabledButton:active {
+ background-color: var(--button-disabled-bg) !important;
+ transform: none !important;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
\ No newline at end of file
diff --git a/my-app/src/components/forms/ParticipantsSelector.jsx b/my-app/src/components/forms/ParticipantsSelector.jsx
index 4a7a621..9c2b5f3 100644
--- a/my-app/src/components/forms/ParticipantsSelector.jsx
+++ b/my-app/src/components/forms/ParticipantsSelector.jsx
@@ -76,6 +76,17 @@ export function ParticipantsSelector({ compact = false }) {
itemRefs.current = [];
};
+ const handleInputBlur = (e) => {
+ // Small delay to allow click events on dropdown items to fire first
+ setTimeout(() => {
+ // Only close if the new focus target is not within our dropdown
+ if (!dropdownRef.current?.contains(document.activeElement)) {
+ setIsDropdownOpen(false);
+ setFocusedIndex(-1);
+ }
+ }, 150);
+ };
+
const handleInputChange = (e) => {
setSearchTerm(e.target.value);
setIsDropdownOpen(true);
@@ -169,6 +180,7 @@ export function ParticipantsSelector({ compact = false }) {
onChange={handleInputChange}
onFocus={handleInputFocus}
onClick={handleInputClick}
+ onBlur={handleInputBlur}
onKeyDown={handleKeyDown}
placeholder="Search for participants..."
className={compact ? styles.compactSearchInput : styles.searchInput}
@@ -196,6 +208,10 @@ export function ParticipantsSelector({ compact = false }) {
ref={el => itemRefs.current[index] = el}
className={`${styles.dropdownItem} ${isPersonSelected(person.name) ? styles.selectedItem : ''} ${index === focusedIndex ? styles.focusedItem : ''}`}
onClick={() => handleSelectPerson(person)}
+ onMouseDown={(e) => {
+ e.preventDefault(); // Prevent blur from firing
+ handleSelectPerson(person);
+ }}
role="option"
aria-selected={isPersonSelected(person.name)}
>
@@ -232,6 +248,10 @@ export function ParticipantsSelector({ compact = false }) {
ref={el => itemRefs.current[index] = el}
className={`${styles.dropdownItem} ${isPersonSelected(person.name) ? styles.selectedItem : ''} ${index === focusedIndex ? styles.focusedItem : ''}`}
onClick={() => handleSelectPerson(person)}
+ onMouseDown={(e) => {
+ e.preventDefault(); // Prevent blur from firing
+ handleSelectPerson(person);
+ }}
role="option"
aria-selected={isPersonSelected(person.name)}
>
diff --git a/my-app/src/components/forms/ParticipantsSelector.module.css b/my-app/src/components/forms/ParticipantsSelector.module.css
index ce1fc9a..796be70 100644
--- a/my-app/src/components/forms/ParticipantsSelector.module.css
+++ b/my-app/src/components/forms/ParticipantsSelector.module.css
@@ -1,5 +1,6 @@
.container {
position: relative;
+ margin-bottom: 4rem;
}
.elementHeading {
@@ -18,7 +19,6 @@
flex-wrap: wrap;
gap: 0.5rem;
padding: 0;
- margin-top: 1rem;
}
.participantChip {
@@ -310,4 +310,5 @@
outline: 2px solid var(--color-primary);
outline-offset: 2px;
border-color: var(--color-primary);
-}
\ No newline at end of file
+}
+
diff --git a/my-app/src/components/layout/Navigation.jsx b/my-app/src/components/layout/Navigation.jsx
index 989f446..3ac0ad7 100644
--- a/my-app/src/components/layout/Navigation.jsx
+++ b/my-app/src/components/layout/Navigation.jsx
@@ -19,18 +19,28 @@ const Navigation = () => {
// Prevent body scroll when mobile menu is open
useEffect(() => {
+ // Store original overflow value
+ const originalOverflow = document.body.style.overflow;
+
if (menuOpen) {
document.body.style.overflow = 'hidden';
} else {
- document.body.style.overflow = 'unset';
+ document.body.style.overflow = originalOverflow || '';
}
- // Cleanup on unmount
+ // Cleanup on unmount - always restore scroll
return () => {
- document.body.style.overflow = 'unset';
+ document.body.style.overflow = originalOverflow || '';
};
}, [menuOpen]);
+ // Additional cleanup on component unmount
+ useEffect(() => {
+ return () => {
+ document.body.style.overflow = '';
+ };
+ }, []);
+
const toggleCourses = () => {
setCoursesOpen(!coursesOpen);
};
@@ -60,7 +70,7 @@ const Navigation = () => {
-

+
Studentportalen
diff --git a/my-app/src/components/layout/Navigation.module.css b/my-app/src/components/layout/Navigation.module.css
index 872cb95..d92f9c5 100644
--- a/my-app/src/components/layout/Navigation.module.css
+++ b/my-app/src/components/layout/Navigation.module.css
@@ -6,6 +6,8 @@
display: flex;
flex-direction: column;
background-color: var(--bg-secondary);
+ background-color: var(--su-blue);
+ color: white;
box-shadow: var(--shadow-md);
position: fixed;
top: 0;
@@ -27,7 +29,6 @@
align-items: center;
gap: var(--spacing-lg);
font-weight: var(--font-weight-semibold);
- color: var(--header-brand-color);
}
.right {
@@ -39,18 +40,20 @@
.logo img {
height: 40px;
transition: filter 0.2s ease;
+ color: white;
}
.brandText {
font-size: 1.1rem;
font-weight: var(--font-weight-semibold);
- color: var(--header-brand-color);
+ color: white;
}
.menuIcon {
font-size: 24px;
cursor: pointer;
color: var(--text-primary);
+ color: white;
padding: var(--spacing-xs);
border-radius: var(--border-radius-md);
transition: var(--transition-fast);
diff --git a/my-app/src/components/ui/Card.jsx b/my-app/src/components/ui/Card.jsx
index 7eca5cc..f851527 100644
--- a/my-app/src/components/ui/Card.jsx
+++ b/my-app/src/components/ui/Card.jsx
@@ -1,17 +1,55 @@
import React from 'react';
-import styles from './Card.module.css'; // Import the CSS Module
+import styles from './Card.module.css';
+
+const Card = ({ imageUrl, header, subheader, features = [], onClick, as: Component = 'div' }) => {
+ const cardProps = {
+ className: styles.card,
+ onClick,
+ ...(Component === 'div' && {
+ role: "button",
+ tabIndex: 0,
+ onKeyDown: (e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onClick?.(e);
+ }
+ }
+ })
+ };
-const Card = ({ imageUrl, header, subheader }) => {
return (
-
-
-
-
{header}
-
-
{subheader}
+
+
+
+
+
{header}
+ {subheader && (
+
{subheader}
+ )}
+ {features.length > 0 && (
+
+ {features.map((feature, index) => (
+
+ {feature.icon && (
+
+ {feature.icon}
+
+ )}
+
{feature.text}
+
+ ))}
+
+ )}
+
+
+
-
+
);
};
diff --git a/my-app/src/components/ui/Card.module.css b/my-app/src/components/ui/Card.module.css
index b7d443b..0a00bdf 100644
--- a/my-app/src/components/ui/Card.module.css
+++ b/my-app/src/components/ui/Card.module.css
@@ -1,46 +1,116 @@
.card {
- width: 100%; /* Adjust width as needed */
- height: 300px; /* Adjust height as needed */
+ display: flex;
+ background: var(--bg-primary);
+ border: 2px solid var(--border-light);
+ border-radius: var(--border-radius-xl);
+ overflow: hidden;
+ cursor: pointer;
+ transition: all var(--transition-medium);
+ box-shadow: var(--shadow-sm);
+ text-decoration: none;
+ color: inherit;
+}
+
+.card:hover {
+ border-color: var(--color-primary);
+ box-shadow: var(--shadow-lg);
+ transform: translateY(-1px);
+}
+
+.card:focus {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+}
+
+.imageSection {
+ width: 120px;
+ min-width: 120px;
+ height: 100px;
background-size: cover;
background-position: center;
+ background-color: var(--bg-secondary);
position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
}
-.gradientOverlay {
+.imageOverlay {
position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(to bottom, transparent, #05305E);
- display: flex;
- align-items: center;
- justify-content: center;
+ inset: 0;
+ background: linear-gradient(45deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.05));
}
-.textContainer {
- text-align: center;
- color: white;
- padding: 20px;
+.contentSection {
+ flex: 1;
+ padding: var(--spacing-lg);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: var(--spacing-sm);
}
.header {
margin: 0;
- font-size: 2em; /* Adjust font size as needed */
-}
-
-.line {
- width: 100%;
- height: 3px; /* Adjust thickness as needed */
- background-color: white;
- margin: 10px auto;
+ font-size: var(--font-size-2xl);
+ font-weight: var(--font-weight-semibold);
+ color: var(--text-primary);
+ line-height: var(--line-height-tight);
}
.subheader {
margin: 0;
- font-size: 1.2em; /* Adjust font size as needed */
- font-weight: normal;
+ font-size: var(--font-size-md);
+ font-weight: var(--font-weight-normal);
+ color: var(--text-secondary);
+ line-height: var(--line-height-normal);
+}
+
+.features {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-sm);
+ margin-top: var(--spacing-xs);
+}
+
+.feature {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+ font-size: var(--font-size-sm);
+ color: var(--text-tertiary);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ background: var(--bg-secondary);
+ border-radius: var(--border-radius-sm);
+}
+
+.featureIcon {
+ width: 14px;
+ height: 14px;
+ color: var(--color-primary);
+}
+
+.actionSection {
+ width: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, var(--color-primary), var(--color-primary-hover));
+ position: relative;
+}
+
+.actionSection::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: rgba(255, 255, 255, 0.1);
+ opacity: 0;
+ transition: opacity var(--transition-fast);
+}
+
+.card:hover .actionSection::before {
+ opacity: 1;
+}
+
+.actionIcon {
+ width: 24px;
+ height: 24px;
+ color: white;
}
diff --git a/my-app/src/components/ui/Dropdown.jsx b/my-app/src/components/ui/Dropdown.jsx
index 5807b2f..04fa872 100644
--- a/my-app/src/components/ui/Dropdown.jsx
+++ b/my-app/src/components/ui/Dropdown.jsx
@@ -3,9 +3,9 @@ import styles from "./Dropdown.module.css";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
-const Dropdown = ({ options, value, onChange, placeholder = {value: "", label: "Select an option"}, disabledOptions = false }) => {
+const Dropdown = ({ options, value, onChange, placeholder = {value: "", label: "Select an option"}, disabledOptions = false, className }) => {
return (
-
+
- {/* Show modal when a time slot is selected and not using inline form */}
- {!useInlineForm && booking.selectedStartIndex !== null && (
+ {/* Show modal when a time slot is selected and using modal form type */}
+ {useModal && booking.selectedStartIndex !== null && (
booking.resetTimeSelections()}
+ onNavigateToDetails={handleNavigateToDetails}
isOpen={true}
/>
)}
diff --git a/my-app/src/helpers.jsx b/my-app/src/helpers.jsx
index 35aa97d..161c5f4 100644
--- a/my-app/src/helpers.jsx
+++ b/my-app/src/helpers.jsx
@@ -15,27 +15,62 @@ 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';
}
+}
+
+export function formatBookingDate(date) {
+ console.log('formatBookingDate called with:', date);
+
+ if (!date || !date.day || !date.month || !date.year) {
+ console.log('Date validation failed:', { date, day: date?.day, month: date?.month, year: date?.year });
+ return 'Ogiltigt datum';
+ }
+
+ const months = ["januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december"];
+
+ const monthIndex = date.month;
+ console.log('Month index:', monthIndex);
+ const monthName = months[monthIndex - 1]; // month is 1-based, array is 0-based
+ console.log('Month name:', monthName);
+
+ if (!monthName) {
+ console.log('Month name not found, falling back');
+ // If month is out of range, fall back to simple format
+ return `${date.day}/${date.month}/${date.year}`;
+ }
+
+ const result = `${date.day} ${monthName} ${date.year}`;
+ console.log('Final result:', result);
+ return result;
}
\ No newline at end of file
diff --git a/my-app/src/hooks/useBookingState.js b/my-app/src/hooks/useBookingState.js
index e77b0c4..3eb422e 100644
--- a/my-app/src/hooks/useBookingState.js
+++ b/my-app/src/hooks/useBookingState.js
@@ -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,
]);
}
\ No newline at end of file
diff --git a/my-app/src/index.css b/my-app/src/index.css
index 3192f77..92a1776 100644
--- a/my-app/src/index.css
+++ b/my-app/src/index.css
@@ -30,6 +30,8 @@ body {
display: flex;
min-width: 320px;
min-height: 100vh;
+ background-color: var(--bg-primary);
+ color: var(--text-primary);
box-sizing: border-box;
width: 100vw;
diff --git a/my-app/src/pages/BookingDetails.jsx b/my-app/src/pages/BookingDetails.jsx
new file mode 100644
index 0000000..6abb310
--- /dev/null
+++ b/my-app/src/pages/BookingDetails.jsx
@@ -0,0 +1,132 @@
+import React, { useEffect, useState } 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, formatBookingDate, getTimeFromIndex } from '../helpers';
+
+export function BookingDetails({ addBooking }) {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { getEffectiveToday } = useSettingsContext();
+ const booking = useBookingState(addBooking, getEffectiveToday());
+ const [isAccordionOpen, setIsAccordionOpen] = useState(false);
+
+ 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();
+ };
+
+ // Check if save button should be enabled (at least one other participant selected)
+ const isSaveButtonEnabled = booking.participants && booking.participants.length > 0;
+
+ const toggleAccordion = () => {
+ setIsAccordionOpen(!isAccordionOpen);
+ };
+
+ return (
+
+
+
+
Ny bokning
+
+
+ {booking.selectedDate ? formatBookingDate(booking.selectedDate) : 'Välj datum'}
+
+
+ {getTimeFromIndex(booking.selectedStartIndex)} - {getTimeFromIndex(booking.selectedEndIndex)}
+
+
+
+
+
+
+
+
+
+
+
+
+ Lokal: {booking.assignedRoom || 'G5:12'}
+
+ {isAccordionOpen ? '▲' : '▼'}
+
+
+ {isAccordionOpen && (
+
+
+ 5 platser
+
+
+ Plats:
+ Röda avdelningen
+
+
+ Utrustning:
+ Whiteboard, TV, Tangentbord, Mus.
+
+
+ ⚠️ Status:
+ HDMI-kabel glappar.
+
+
+ )}
+
+
+
+
+ {!isSaveButtonEnabled && (
+
+ Lägg till minst en deltagare för att boka
+
+ )}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/my-app/src/pages/BookingDetails.module.css b/my-app/src/pages/BookingDetails.module.css
new file mode 100644
index 0000000..3ca2c19
--- /dev/null
+++ b/my-app/src/pages/BookingDetails.module.css
@@ -0,0 +1,242 @@
+.pageContainer {
+ margin: 0 auto;
+ 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;
+ padding: 0 1rem;
+ border-bottom: 1px solid var(--border-light);
+ background-color: var(--bg-primary);
+ position: sticky;
+ top: 4rem;
+ z-index: 999;
+}
+
+.mainSection {
+ padding: 1rem;
+ padding-bottom: 8rem; /* Space for sticky footer */
+}
+
+.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;
+ width: fit-content;
+ margin-bottom: 2rem;
+}
+
+.backButton:hover {
+ background-color: var(--bg-secondary);
+}
+
+.pageTitle {
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ text-transform: uppercase;
+}
+
+.bookingInfo {
+ margin-bottom: 2rem;
+}
+
+.dateTimeSection {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 1rem;
+}
+
+.dateContainer,
+.timeContainer {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.date, .time {
+ font-size: 0.9rem;
+ font-weight: 300;
+ color: var(--text-primary);
+}
+
+.roomVisualization {
+ margin-bottom: 2rem;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.roomArea {
+ height: 200px;
+ background-image: url('./grupprum.jpg');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ position: relative;
+}
+
+.roomInfo {
+ border-top: 2px solid var(--su-sky);
+ background: var(--su-blue);
+ color: white;
+ padding: 1rem;
+ font-weight: 500;
+ font-size: 1.1rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.roomHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+}
+
+.dropdown {
+ font-size: 0.8rem;
+ opacity: 0.8;
+ transition: transform 0.3s ease;
+}
+
+.dropdown.rotated {
+ transform: rotate(180deg);
+}
+
+.roomDetails {
+ margin-top: 1rem;
+ padding-top: 1rem;
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ animation: slideDown 0.3s ease;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.roomDetailItem {
+ display: flex;
+ gap: 0.5rem;
+ align-items: flex-start;
+}
+
+.roomDetailItem:first-child {
+ font-size: 1.2rem;
+ font-weight: 600;
+}
+
+.roomDetailItem:first-child .roomDetailLabel {
+ color: white;
+}
+
+.roomDetailLabel {
+ color: rgba(255, 255, 255, 0.9);
+ font-weight: 500;
+ min-width: fit-content;
+}
+
+.roomDetailValue {
+ color: white;
+ flex: 1;
+}
+
+.formContainer {
+ display: flex;
+ flex-direction: column;
+}
+
+.footer {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ padding: 1rem;
+ background-color: var(--bg-primary);
+ border-top: 1px solid var(--border-light);
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+ z-index: 1000;
+}
+
+.saveButton {
+ width: 100%;
+ padding: 1rem;
+ background-color: var(--su-blue);
+ background-color: #2D59F3;
+ color: var(--su-white);
+ border: none;
+ border-radius: 0.5rem;
+ font-size: 1.1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ box-sizing: border-box;
+ box-shadow: 0 2px 4px rgba(0, 47, 95, 0.2);
+ height: fit-content;
+}
+
+.saveButton:hover {
+ background-color: var(--su-blue-80);
+ box-shadow: 0 4px 8px rgba(0, 47, 95, 0.3);
+ transform: translateY(-1px);
+}
+
+.saveButton:active {
+ transform: translateY(0);
+ box-shadow: 0 1px 2px rgba(0, 47, 95, 0.2);
+}
+
+.saveButton:focus {
+ outline: 2px solid var(--su-sky);
+ outline-offset: 2px;
+}
+
+.disabledButton {
+ background-color: var(--bg-tertiary, #e5e5e5) !important;
+ color: var(--text-secondary, #666) !important;
+ cursor: not-allowed !important;
+ box-shadow: none !important;
+ opacity: 0.8;
+ border: 1px solid var(--border-light, #ddd) !important;
+}
+
+.disabledButton:hover {
+ background-color: var(--bg-tertiary, #e5e5e5) !important;
+ box-shadow: none !important;
+ transform: none !important;
+}
+
+.disabledButton:active {
+ transform: none !important;
+ box-shadow: none !important;
+}
+
+.participantRequirement {
+ color: var(--text-secondary, #666);
+ font-size: 0.9rem;
+ text-align: center;
+ margin-bottom: 0.5rem;
+ border-radius: 0.25rem;
+}
\ No newline at end of file
diff --git a/my-app/src/pages/BookingSettings.jsx b/my-app/src/pages/BookingSettings.jsx
index 2b2e67b..60f45e7 100644
--- a/my-app/src/pages/BookingSettings.jsx
+++ b/my-app/src/pages/BookingSettings.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { useSettingsContext } from '../context/SettingsContext';
import styles from './BookingSettings.module.css';
@@ -31,6 +31,11 @@ export function BookingSettings() {
return `${hours}:${minutes === 0 ? '00' : '30'}`;
};
+ // Ensure body scroll is enabled when component mounts
+ useEffect(() => {
+ document.body.style.overflow = '';
+ }, []);
+
const effectiveToday = getEffectiveToday();
const isUsingMockDate = settings.mockToday !== null;
@@ -133,7 +138,7 @@ export function BookingSettings() {
- Current: {settings.bookingFormType === 'inline' ? 'Inline Form' : 'Modal Popup'}
+ Current:
+ {settings.bookingFormType === 'inline' ? 'Inline Form' :
+ settings.bookingFormType === 'modal' ? 'Modal Popup' :
+ 'Inline Modal'}
+
+
+
+ Inline Form: All fields in one form
+ Modal Popup: Time selection in popup, then details page
+ Inline Modal: Time selection inline, then details page
diff --git a/my-app/src/pages/NewBooking.jsx b/my-app/src/pages/NewBooking.jsx
index 80c79b0..e3dcf41 100644
--- a/my-app/src/pages/NewBooking.jsx
+++ b/my-app/src/pages/NewBooking.jsx
@@ -2,8 +2,6 @@ import React, { useState, useEffect } from 'react';
import styles from './NewBooking.module.css';
import { TimeCardContainer } from '../components/ui/TimeCardContainer';
import { BookingDatePicker } from '../components/forms/BookingDatePicker';
-import { BookingTitleField } from '../components/forms/BookingTitleField';
-import { ParticipantsSelector } from '../components/forms/ParticipantsSelector';
import { RoomSelectionField } from '../components/forms/RoomSelectionField';
import { BookingLengthField } from '../components/forms/BookingLengthField';
import { useBookingState } from '../hooks/useBookingState';
@@ -64,13 +62,6 @@ export function NewBooking({ addBooking }) {
Boka litet grupprum
- {/* Only show title and participants fields in modal mode */}
- {!useInlineForm && (
- <>
-
-
- >
- )}
diff --git a/my-app/src/pages/RoomBooking.jsx b/my-app/src/pages/RoomBooking.jsx
index 6769ddf..2a2c7cf 100644
--- a/my-app/src/pages/RoomBooking.jsx
+++ b/my-app/src/pages/RoomBooking.jsx
@@ -10,6 +10,8 @@ export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, o
const { settings } = useSettingsContext();
useEffect(() => {
+ // Ensure body scroll is enabled and scroll to top
+ document.body.style.overflow = '';
window.scrollTo(0, 0);
}, []);
@@ -31,6 +33,10 @@ export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, o
)}
Lokalbokning
+ Ny bokning
+
+
+
Mina bokingar
- Ny bokning
-
-
-
);
}
\ No newline at end of file
diff --git a/my-app/src/react-aria-starter/src/Calendar.css b/my-app/src/react-aria-starter/src/Calendar.css
index b7ad985..5bf1f90 100644
--- a/my-app/src/react-aria-starter/src/Calendar.css
+++ b/my-app/src/react-aria-starter/src/Calendar.css
@@ -4,7 +4,7 @@
.react-aria-Calendar {
width: fit-content;
max-width: 100%;
- color: var(--text-color);
+ color: var(--text-primary);
header {
display: flex;
@@ -44,46 +44,46 @@
}
&:hover:not([data-selected]):not([data-disabled]):not([data-unavailable]) {
- background-color: var(--highlight-hover);
+ background-color: var(--bg-muted);
}
&[data-pressed] {
- background: var(--gray-100);
+ background: var(--bg-tertiary);
}
&[data-focus-visible] {
- outline: 2px solid var(--focus-ring-color);
+ outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
&[data-selected] {
- background: var(--highlight-background);
- color: var(--highlight-foreground);
+ background: var(--color-primary);
+ color: var(--color-white);
}
}
.react-aria-CalendarCell {
&[data-disabled] {
- color: var(--text-color-disabled);
+ color: var(--text-disabled);
}
}
.react-aria-CalendarCell {
&[data-unavailable] {
text-decoration: line-through;
- color: var(--text-color-disabled);
+ color: var(--text-disabled);
}
}
.react-aria-CalendarCell {
&[data-invalid] {
- background: var(--invalid-color);
- color: var(--highlight-foreground);
+ background: var(--notification-error-bg);
+ color: var(--notification-error-title);
}
}
[slot=errorMessage] {
font-size: 12px;
- color: var(--invalid-color);
+ color: var(--notification-error-title);
}
}
diff --git a/my-app/src/react-aria-starter/src/DatePicker.css b/my-app/src/react-aria-starter/src/DatePicker.css
index aeea3dd..48d02c1 100644
--- a/my-app/src/react-aria-starter/src/DatePicker.css
+++ b/my-app/src/react-aria-starter/src/DatePicker.css
@@ -7,7 +7,7 @@
@import "./theme.css";
.react-aria-DatePicker {
- color: var(--text-color);
+ color: var(--text-primary);
background-color: var(--bg-secondary);
padding: var(--spacing-md);
border: 1px solid var(--border-light);
@@ -39,38 +39,34 @@
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s, opacity 0.2s, color 0.2s;
- color: var(--chevron-button-color);
+ color: var(--text-primary);
}
.chevron-button:hover:not(:disabled) {
- background-color: var(--highlight-hover);
+ background-color: var(--bg-muted);
}
.chevron-button:active:not(:disabled) {
- background-color: var(--highlight-pressed);
+ background-color: var(--bg-tertiary);
}
.chevron-button:disabled {
cursor: default;
- color: var(--chevron-button-disabled-color);
+ color: var(--text-disabled);
opacity: 0.4;
}
.chevron-button:focus-visible {
- outline: 2px solid var(--focus-ring-color);
+ outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
.react-aria-Button {
- /*background: var(--highlight-background);*/
- /*color: var(--highlight-foreground);*/
- border: 2px solid var(--field-background);
+ background: var(--button-secondary-bg);
+ color: var(--button-secondary-text);
+ border: 1px solid var(--border-light);
forced-color-adjust: none;
- border-radius: 4px;
- /*border: none;*/
- border: 1px solid var(--border-color);
- /*width: 1.429rem;*/
- /*height: 1.429rem;*/
+ border-radius: var(--border-radius-sm);
width: fit-content;
padding: 0.5rem 1rem;
font-size: 1rem;
@@ -78,12 +74,11 @@
&[data-pressed] {
box-shadow: none;
- /*background: var(--highlight-background);*/
- background: var(--button-background-pressed);
+ background: var(--button-secondary-hover-bg);
}
&[data-focus-visible] {
- outline: 2px solid var(--focus-ring-color);
+ outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
}
@@ -95,12 +90,12 @@
justify-content: space-between !important;
gap: 0.75rem !important;
cursor: pointer !important;
- background: var(--field-background) !important;
- border: 1px solid var(--border-color) !important;
+ background: var(--input-bg) !important;
+ border: 1px solid var(--input-border) !important;
border-radius: 8px !important;
padding: 12px 16px !important;
font-weight: 500 !important;
- color: var(--field-text-color) !important;
+ color: var(--input-text) !important;
transition: all 0.2s ease !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
white-space: nowrap !important;
@@ -113,21 +108,21 @@
}
.calendar-button:hover {
- border-color: var(--border-color-hover) !important;
+ border-color: var(--color-primary) !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
}
.calendar-button[data-pressed] {
- background: var(--button-background-pressed) !important;
- border-color: var(--border-color-pressed) !important;
+ background: var(--button-secondary-hover-bg) !important;
+ border-color: var(--color-primary) !important;
transform: translateY(1px) !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
}
.calendar-button[data-focus-visible] {
- outline: 2px solid var(--focus-ring-color) !important;
+ outline: 2px solid var(--color-primary) !important;
outline-offset: 2px !important;
- border-color: var(--focus-ring-color) !important;
+ border-color: var(--color-primary) !important;
}
.calendar-date {
@@ -144,8 +139,6 @@
.react-aria-Popover[data-trigger=DatePicker] {
max-width: unset;
- transform: translateX(-50%);
- left: 50% !important;
}
.react-aria-DatePicker {
@@ -161,7 +154,7 @@
.react-aria-FieldError {
font-size: 12px;
- color: var(--invalid-color);
+ color: var(--notification-error-title);
}
[slot=description] {
diff --git a/my-app/src/react-aria-starter/src/DatePicker.tsx b/my-app/src/react-aria-starter/src/DatePicker.tsx
index a07e62c..0b1368d 100644
--- a/my-app/src/react-aria-starter/src/DatePicker.tsx
+++ b/my-app/src/react-aria-starter/src/DatePicker.tsx
@@ -90,7 +90,7 @@ export function DatePicker
(
{description && {description}}
{errorMessage}
-
+