From d89e25633fe3080712d8353389948ff7361ced7c Mon Sep 17 00:00:00 2001 From: Jacob Reinikainen <80760206+jazzjacob@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:39:40 +0200 Subject: [PATCH 01/13] booking confirmation and stuff --- my-app/src/AppRoutes.jsx | 2 + .../booking/InlineModalBookingForm.module.css | 1 + .../InlineModalExtendedBookingForm.jsx | 159 +++++++++ .../InlineModalExtendedBookingForm.module.css | 31 ++ .../components/forms/ParticipantsSelector.jsx | 2 +- .../forms/ParticipantsSelector.module.css | 1 + .../src/components/ui/TimeCardContainer.jsx | 17 +- my-app/src/pages/BookingConfirmation.jsx | 122 +++++++ .../src/pages/BookingConfirmation.module.css | 314 ++++++++++++++++++ my-app/src/pages/BookingDetails.module.css | 38 +++ my-app/src/pages/BookingSettings.jsx | 7 +- my-app/src/pages/NewBooking.module.css | 1 - .../src/react-aria-starter/src/DatePicker.css | 9 +- my-app/src/styles/variables.css | 2 +- 14 files changed, 699 insertions(+), 7 deletions(-) create mode 100644 my-app/src/components/booking/InlineModalExtendedBookingForm.jsx create mode 100644 my-app/src/components/booking/InlineModalExtendedBookingForm.module.css create mode 100644 my-app/src/pages/BookingConfirmation.jsx create mode 100644 my-app/src/pages/BookingConfirmation.module.css diff --git a/my-app/src/AppRoutes.jsx b/my-app/src/AppRoutes.jsx index 76e0a30..0ce08f6 100644 --- a/my-app/src/AppRoutes.jsx +++ b/my-app/src/AppRoutes.jsx @@ -6,6 +6,7 @@ import Layout from './Layout'; import { RoomBooking } from './pages/RoomBooking'; import { NewBooking } from './pages/NewBooking'; import { BookingDetails } from './pages/BookingDetails'; +import { BookingConfirmation } from './pages/BookingConfirmation'; import { BookingSettings } from './pages/BookingSettings'; import { CourseSchedule } from './pages/CourseSchedule'; import { CourseScheduleView } from './pages/CourseScheduleView'; @@ -121,6 +122,7 @@ const AppRoutes = () => { setShowSuccessBanner(false)} onBookingUpdate={updateBooking} onBookingDelete={deleteBooking} showDeleteBanner={showDeleteBanner} lastDeletedBooking={lastDeletedBooking} onDismissDeleteBanner={() => setShowDeleteBanner(false)} />} /> } /> } /> + } /> } /> } /> } /> diff --git a/my-app/src/components/booking/InlineModalBookingForm.module.css b/my-app/src/components/booking/InlineModalBookingForm.module.css index 17294bc..c0e5448 100644 --- a/my-app/src/components/booking/InlineModalBookingForm.module.css +++ b/my-app/src/components/booking/InlineModalBookingForm.module.css @@ -10,6 +10,7 @@ flex-basis: 100%; max-width: none; position: relative; + z-index: 1; } /* Arrow pointing to left card */ diff --git a/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx b/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx new file mode 100644 index 0000000..dc82332 --- /dev/null +++ b/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx @@ -0,0 +1,159 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Button } from 'react-aria-components'; +import { useNavigate } from 'react-router-dom'; +import { convertDateObjectToString, getTimeFromIndex } from '../../helpers'; +import Dropdown from '../ui/Dropdown'; +import { BookingTitleField } from '../forms/BookingTitleField'; +import { ParticipantsSelector } from '../forms/ParticipantsSelector'; +import { useBookingContext } from '../../context/BookingContext'; +import { useSettingsContext } from '../../context/SettingsContext'; +import styles from './InlineModalBookingForm.module.css'; +import extendedStyles from './InlineModalExtendedBookingForm.module.css'; + +export function InlineModalExtendedBookingForm({ + startTimeIndex, + hoursAvailable, + endTimeIndex, + setEndTimeIndex, + onClose, + onNavigateToDetails, + arrowPointsLeft = true +}) { + const navigate = useNavigate(); + 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 handleNavigateToConfirmation = () => { + if (hasSelectedEndTime) { + // Navigate directly to booking confirmation instead of details + navigate('/booking-confirmation', { + state: { + selectedDate: booking.selectedDate, + selectedStartIndex: booking.selectedStartIndex, + selectedEndIndex: booking.selectedEndIndex, + assignedRoom: booking.assignedRoom, + title: booking.title, + participants: booking.participants + } + }); + } + }; + + return ( +
+ {/* Header */} +
+ {/* Time Selection */} +
+
+ +
+
+ + {/* Title Field - Compact */} +
+ +
+ + {/* Participants Field - Compact */} +
+ +
+
+ + {/* Actions */} +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/my-app/src/components/booking/InlineModalExtendedBookingForm.module.css b/my-app/src/components/booking/InlineModalExtendedBookingForm.module.css new file mode 100644 index 0000000..e32da17 --- /dev/null +++ b/my-app/src/components/booking/InlineModalExtendedBookingForm.module.css @@ -0,0 +1,31 @@ +/* Import base styles from the regular inline modal form */ +@import './InlineModalBookingForm.module.css'; + +/* Extended form specific styles to ensure height alignment */ +.section { + margin-bottom: var(--spacing-lg); /* Reduce spacing between sections */ +} + +/* Ensure all form inputs have consistent height */ +.compactInput { + height: 2.5rem; /* Match dropdown height */ + display: flex; + align-items: center; + box-sizing: border-box; +} + +/* Override compact styles to match dropdown height exactly */ +:global(.compactTextInput), +:global(.compactSearchInput) { + height: 2.5rem !important; + display: flex !important; + align-items: center !important; + padding: 0.5rem 1rem !important; + box-sizing: border-box !important; +} + +/* Ensure headings are aligned */ +.compactHeading { + margin-bottom: 0.4rem; + margin-top: 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 9c2b5f3..fbfea5c 100644 --- a/my-app/src/components/forms/ParticipantsSelector.jsx +++ b/my-app/src/components/forms/ParticipantsSelector.jsx @@ -182,7 +182,7 @@ export function ParticipantsSelector({ compact = false }) { onClick={handleInputClick} onBlur={handleInputBlur} onKeyDown={handleKeyDown} - placeholder="Search for participants..." + placeholder="Sök deltagare..." className={compact ? styles.compactSearchInput : styles.searchInput} role="combobox" aria-expanded={isDropdownOpen} diff --git a/my-app/src/components/forms/ParticipantsSelector.module.css b/my-app/src/components/forms/ParticipantsSelector.module.css index 796be70..0e66af1 100644 --- a/my-app/src/components/forms/ParticipantsSelector.module.css +++ b/my-app/src/components/forms/ParticipantsSelector.module.css @@ -15,6 +15,7 @@ } .selectedParticipants { + margin-top: 0.5rem; display: flex; flex-wrap: wrap; gap: 0.5rem; diff --git a/my-app/src/components/ui/TimeCardContainer.jsx b/my-app/src/components/ui/TimeCardContainer.jsx index dae6125..edf949c 100644 --- a/my-app/src/components/ui/TimeCardContainer.jsx +++ b/my-app/src/components/ui/TimeCardContainer.jsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import TimeCard from './TimeCard'; import { InlineBookingForm } from '../booking/InlineBookingForm'; import { InlineModalBookingForm } from '../booking/InlineModalBookingForm'; +import { InlineModalExtendedBookingForm } from '../booking/InlineModalExtendedBookingForm'; import { BookingModal } from '../booking/BookingModal'; import styles from './TimeCardContainer.module.css'; import modalStyles from '../booking/BookingModal.module.css'; @@ -19,6 +20,7 @@ export function TimeCardContainer() { // Check booking form type const useInlineForm = settings.bookingFormType === 'inline'; const useInlineModal = settings.bookingFormType === 'inline-modal'; + const useInlineModalExtended = settings.bookingFormType === 'inline-modal-extended'; const useModal = settings.bookingFormType === 'modal'; const handleNavigateToDetails = () => { @@ -143,7 +145,7 @@ export function TimeCardContainer() { // Add inline booking form after the pair that contains the selected time card // Cards are laid out in pairs: (0,1), (2,3), (4,5), etc. - if ((useInlineForm || useInlineModal) && booking.selectedStartIndex !== null) { + if ((useInlineForm || useInlineModal || useInlineModalExtended) && booking.selectedStartIndex !== null) { const selectedPairStart = Math.floor(booking.selectedStartIndex / 2) * 2; const selectedPairEnd = selectedPairStart + 1; @@ -174,6 +176,19 @@ export function TimeCardContainer() { arrowPointsLeft={isLeftCard} /> ); + } else if (useInlineModalExtended) { + elements.push( + booking.resetTimeSelections()} + onNavigateToDetails={handleNavigateToDetails} + arrowPointsLeft={isLeftCard} + /> + ); } } } diff --git a/my-app/src/pages/BookingConfirmation.jsx b/my-app/src/pages/BookingConfirmation.jsx new file mode 100644 index 0000000..d94a815 --- /dev/null +++ b/my-app/src/pages/BookingConfirmation.jsx @@ -0,0 +1,122 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import styles from './BookingConfirmation.module.css'; +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 BookingConfirmation({ addBooking }) { + const navigate = useNavigate(); + const location = useLocation(); + const { getEffectiveToday, getCurrentUser } = useSettingsContext(); + const booking = useBookingState(addBooking, getEffectiveToday()); + + useEffect(() => { + window.scrollTo(0, 0); + // Automatically save the booking when the page loads + if (location.state && booking) { + setTimeout(() => { + booking.handleSave(); + }, 100); // Small delay to ensure state is populated + } + }, [location.state]); + + // 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 handleBackToBookings = () => { + navigate('/'); + }; + + const handleNewBooking = () => { + navigate('/new-booking'); + }; + + // Get current user and all participants + const currentUser = getCurrentUser(); + const allParticipants = [currentUser, ...(booking.participants || [])]; + + return ( + +
+ {/* Combined Confirmation and Room Section */} +
+ {/* Booking Confirmation */} +
+
+
+
+

+ Booking confirmed: "{booking.title}" +

+
+ {booking.assignedRoom || 'G5:12'} • {booking.selectedDate ? formatBookingDate(booking.selectedDate) : 'Välj datum'} • {getTimeFromIndex(booking.selectedStartIndex)}-{getTimeFromIndex(booking.selectedEndIndex)} • {allParticipants.map(p => p.name).join(', ')} +
+
+
+
+ + {/* Room Visualization */} +
+
+
+
+ Lokal: {booking.assignedRoom || 'G5:12'} +
+
+
+ 5 platser +
+
+ Plats: + Röda avdelningen +
+
+ Utrustning: + Whiteboard, TV, Tangentbord, Mus +
+
+ ⚠️ Status: + HDMI-kabel glappar +
+
+
+
+
+ + {/* Action Buttons */} +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/my-app/src/pages/BookingConfirmation.module.css b/my-app/src/pages/BookingConfirmation.module.css new file mode 100644 index 0000000..a5e382d --- /dev/null +++ b/my-app/src/pages/BookingConfirmation.module.css @@ -0,0 +1,314 @@ +.container { + margin: 0 auto; + padding: 2rem 1rem; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--bg-primary); + width: 100%; + box-sizing: border-box; +} + +/* Combined Section Container */ +.combinedSection { + margin: 2rem 0; + overflow: hidden; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); + width: 100%; +} + +/* Combined Booking Confirmed Section - Banner-like */ +.bookingConfirmed { + padding: var(--spacing-lg) var(--spacing-xl); + background: var(--notification-success-bg); + border: var(--border-width-thin) solid var(--notification-success-border); + border-bottom: none; + position: relative; + box-shadow: var(--shadow-lg); + display: flex; + align-items: center; + justify-content: center; +} + +@keyframes shimmer { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } +} + +.successIcon { + width: 2rem; + height: 2rem; + background: var(--notification-success-icon-bg); + color: var(--notification-success-icon-text); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-size-xl); + font-weight: var(--font-weight-bold); + flex-shrink: 0; + margin-right: var(--spacing-lg); +} + +@keyframes successPulse { + 0% { + transform: scale(0.8); + opacity: 0; + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +.bannerContent { + display: flex; + align-items: center; + gap: var(--spacing-lg); + width: 100%; +} + +.bannerText { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); + flex: 1; + min-width: 0; +} + +.successTitle { + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + color: var(--notification-success-title); + margin: 0; +} + +.bookingDetails { + font-size: var(--font-size-md); + font-weight: var(--font-weight-medium); + color: var(--notification-success-details); + margin: 0; + line-height: 1.4; +} + +.date { + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + color: var(--notification-success-title); +} + +.time { + font-size: var(--font-size-lg); + font-weight: var(--font-weight-medium); + color: var(--notification-success-details); +} + +.room { + font-size: 1.5rem; + font-weight: 600; + color: var(--color-primary); + background: var(--bg-primary); + padding: 0.75rem 1.5rem; + border-radius: 1rem; + border: 2px solid var(--color-primary); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.bookingTitle { + font-weight: var(--font-weight-normal); + font-style: italic; +} + +.participants { + font-size: var(--font-size-md); + color: var(--notification-success-details); + font-weight: var(--font-weight-medium); + line-height: 1.6; + max-width: 80%; +} + +/* Action Buttons */ +.actions { + display: flex; + gap: 1rem; + width: 100%; + max-width: 400px; + box-sizing: border-box; +} + +.primaryButton { + flex: 2; + background: linear-gradient(135deg, var(--color-primary), var(--color-primary-hover)); + color: white; + border: none; + border-radius: 0.75rem; + padding: 1rem 2rem; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + box-sizing: border-box; +} + +.primaryButton:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); +} + +.primaryButton:active { + transform: translateY(0); +} + +.secondaryButton { + flex: 1; + background: var(--bg-secondary); + color: var(--text-primary); + border: 2px solid var(--border-light); + border-radius: 0.75rem; + padding: 1rem 2rem; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-sizing: border-box; +} + +.secondaryButton:hover { + background: var(--bg-tertiary); + border-color: var(--border-medium); +} + +.secondaryButton:active { + transform: translateY(1px); +} + +/* Room Visualization - Connected to booking confirmation */ +.combinedSection .roomVisualization { + margin-bottom: 0; + border-radius: 0; + overflow: hidden; + box-shadow: none; + width: 100%; +} + +.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: 1.5rem; + font-weight: 500; +} + +.roomHeader { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 1.25rem; +} + +.roomDetails { + display: flex; + flex-direction: column; + gap: 0.875rem; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.roomDetailItem { + display: flex; + gap: 0.75rem; + align-items: flex-start; + font-size: 0.95rem; + line-height: 1.4; +} + +.roomDetailItem:first-child { + font-size: 1.05rem; + font-weight: 600; + margin-bottom: 0.25rem; +} + +.roomDetailItem:first-child .roomDetailLabel { + color: white; +} + +.roomDetailLabel { + color: rgba(255, 255, 255, 0.85); + font-weight: 500; + min-width: fit-content; + font-size: inherit; +} + +.roomDetailValue { + color: white; + font-weight: 400; + font-size: inherit; +} + +.spacer { + height: 1rem; +} + +/* Responsive */ +@media (max-width: 640px) { + .container { + padding: 1rem 0.5rem; + } + + .combinedSection { + margin: 1.5rem 0; + } + + .bookingConfirmed { + padding: var(--spacing-md) var(--spacing-lg); + } + + .bannerContent { + flex-direction: column; + text-align: center; + gap: var(--spacing-md); + } + + .successIcon { + margin-right: 0; + margin-bottom: var(--spacing-sm); + } + + .successTitle { + font-size: var(--font-size-lg); + } + + .bookingDetails { + font-size: var(--font-size-sm); + } + + .actions { + flex-direction: column; + } + + .actions button { + flex: none; + } +} \ No newline at end of file diff --git a/my-app/src/pages/BookingDetails.module.css b/my-app/src/pages/BookingDetails.module.css index 3ca2c19..7bc3bd3 100644 --- a/my-app/src/pages/BookingDetails.module.css +++ b/my-app/src/pages/BookingDetails.module.css @@ -47,6 +47,44 @@ text-transform: uppercase; } +/* Booking confirmation summary styles */ +.summaryHeading { + margin: 0; + color: var(--text-tertiary); + font-size: 0.8rem; + font-style: normal; + font-weight: 520; + line-height: normal; + margin-bottom: 0.2rem; + margin-top: 1.5rem; +} + +.summaryValue { + background-color: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 0.5rem; + padding: 1rem; + color: var(--input-text); + font-size: 16px; +} + +.participantsList { + background-color: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 0.5rem; + padding: 1rem; +} + +.participantItem { + padding: 0.5rem 0; + border-bottom: 1px solid var(--border-light); + color: var(--input-text); +} + +.participantItem:last-child { + border-bottom: none; +} + .bookingInfo { margin-bottom: 2rem; } diff --git a/my-app/src/pages/BookingSettings.jsx b/my-app/src/pages/BookingSettings.jsx index 60f45e7..1c4adc2 100644 --- a/my-app/src/pages/BookingSettings.jsx +++ b/my-app/src/pages/BookingSettings.jsx @@ -150,18 +150,21 @@ export function BookingSettings() { +
Current: {settings.bookingFormType === 'inline' ? 'Inline Form' : settings.bookingFormType === 'modal' ? 'Modal Popup' : - 'Inline Modal'} + settings.bookingFormType === 'inline-modal' ? 'Inline Modal' : + 'Inline Modal Extended'}
Inline Form: All fields in one form
Modal Popup: Time selection in popup, then details page
- Inline Modal: Time selection inline, then details page + Inline Modal: Time selection inline, then details page
+ Inline Modal Extended: Like hybrid, plus title and participants
diff --git a/my-app/src/pages/NewBooking.module.css b/my-app/src/pages/NewBooking.module.css index 60f7b4f..7d8c562 100644 --- a/my-app/src/pages/NewBooking.module.css +++ b/my-app/src/pages/NewBooking.module.css @@ -20,7 +20,6 @@ .bookingTimesContainer { margin-top: 2rem; - padding: 2rem; border-radius: 0.3rem; outline: 1px solid var(--border-light); display: flex; diff --git a/my-app/src/react-aria-starter/src/DatePicker.css b/my-app/src/react-aria-starter/src/DatePicker.css index 48d02c1..f22e73f 100644 --- a/my-app/src/react-aria-starter/src/DatePicker.css +++ b/my-app/src/react-aria-starter/src/DatePicker.css @@ -12,9 +12,15 @@ padding: var(--spacing-md); border: 1px solid var(--border-light); position: sticky; + width: 100%; top: 1rem; + z-index: 10; + display: flex; + flex-direction: row; + justify-content: center; .react-aria-Group { + height: fit-content; display: flex; width: fit-content; align-items: center; @@ -22,6 +28,7 @@ flex-direction: row; justify-content: space-between; gap: 2rem; + } .react-aria-Button { @@ -165,6 +172,6 @@ @media (max-width: 1024px) { .react-aria-DatePicker { - top: 5rem; + top: 4rem; } } \ No newline at end of file diff --git a/my-app/src/styles/variables.css b/my-app/src/styles/variables.css index 698c35b..aa3f2ba 100644 --- a/my-app/src/styles/variables.css +++ b/my-app/src/styles/variables.css @@ -432,7 +432,7 @@ /* Background Colors */ /*--bg-primary: #1a1a1a;*/ - --bg-primary: #0F0F0F; + --bg-primary: #1a1919; --bg-secondary: #21211F; --bg-tertiary: #333; --bg-muted: #3a3a3a; -- 2.39.5 From 7def5850a0a8f2d73b1b8fb420540efc89b4ee69 Mon Sep 17 00:00:00 2001 From: Jacob Reinikainen <80760206+jazzjacob@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:51:32 +0200 Subject: [PATCH 02/13] filter button placement --- my-app/src/pages/NewBooking.jsx | 66 +++++++++++++------------- my-app/src/pages/NewBooking.module.css | 18 ++++++- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/my-app/src/pages/NewBooking.jsx b/my-app/src/pages/NewBooking.jsx index e3dcf41..089862c 100644 --- a/my-app/src/pages/NewBooking.jsx +++ b/my-app/src/pages/NewBooking.jsx @@ -67,41 +67,43 @@ export function NewBooking({ addBooking }) { {/* Filter Button */} -
- - - {/* Collapsible Filter Content */} - {showFilters && ( -
-
- - -
- {hasActiveFilters && ( -
- +
+

+ Välj starttid +

+
+ + + {/* Collapsible Filter Content */} + {showFilters && ( +
+
+ +
- )} -
- )} + {hasActiveFilters && ( +
+ +
+ )} +
+ )} +
-

- Lediga tider -

diff --git a/my-app/src/pages/NewBooking.module.css b/my-app/src/pages/NewBooking.module.css index 7d8c562..98c1882 100644 --- a/my-app/src/pages/NewBooking.module.css +++ b/my-app/src/pages/NewBooking.module.css @@ -14,6 +14,13 @@ padding-bottom: 4rem; } +.elementHeading { + font-size: 1.4rem; + margin: 0; + margin-bottom: 0.5rem; + font-weight: 400; +} + .formContainer h2 { font-weight: 529; } @@ -27,6 +34,16 @@ align-items: center; } +.headerAndFilter { + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 11.5rem; + padding-top: 2rem +} + .modalFooter { width: 100%; @@ -147,7 +164,6 @@ /* Filter Section Styles */ .filtersSection { - width: 100%; margin-top: 1rem; margin-bottom: 1.5rem; display: flex; -- 2.39.5 From 490f4bba33af3edd366455dfcd60779bfb7f5347 Mon Sep 17 00:00:00 2001 From: Jacob Reinikainen <80760206+jazzjacob@users.noreply.github.com> Date: Fri, 12 Sep 2025 10:53:39 +0200 Subject: [PATCH 03/13] booking inline modal fixes --- .../booking/InlineModalBookingForm.module.css | 27 +-- .../InlineModalExtendedBookingForm.jsx | 3 + .../InlineModalExtendedBookingForm.module.css | 10 +- ...InlineModalExtendedBookingFormNoLabels.jsx | 161 ++++++++++++++++++ .../components/forms/BookingTitleField.jsx | 8 +- .../components/forms/ParticipantsSelector.jsx | 8 +- .../forms/ParticipantsSelector.module.css | 2 + .../src/components/ui/TimeCardContainer.jsx | 17 +- my-app/src/context/SettingsContext.jsx | 4 + my-app/src/pages/BookingSettings.jsx | 33 +++- my-app/src/pages/NewBooking.jsx | 57 ++++--- my-app/src/pages/NewBooking.module.css | 3 +- 12 files changed, 285 insertions(+), 48 deletions(-) create mode 100644 my-app/src/components/booking/InlineModalExtendedBookingFormNoLabels.jsx diff --git a/my-app/src/components/booking/InlineModalBookingForm.module.css b/my-app/src/components/booking/InlineModalBookingForm.module.css index c0e5448..0bb288e 100644 --- a/my-app/src/components/booking/InlineModalBookingForm.module.css +++ b/my-app/src/components/booking/InlineModalBookingForm.module.css @@ -64,10 +64,11 @@ } .formHeader { - text-align: center; + text-align: start; /*margin-bottom: var(--spacing-2xl);*/ /*padding-bottom: var(--spacing-lg);*/ /*border-bottom: 1px solid var(--border-light);*/ + margin-bottom: 1rem; } .formTitle { @@ -84,7 +85,7 @@ } .section { - margin-bottom: var(--spacing-2xl); + margin-bottom: 1.4rem; } .formField { @@ -94,9 +95,9 @@ } .formField label { - font-size: var(--font-size-sm); - font-weight: var(--font-weight-medium); - color: var(--text-primary); + font-size: 0.75rem; + font-weight: 500; + color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.5px; } @@ -119,8 +120,8 @@ 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); + border: 1px solid var(--modal-cancel-border); + border-radius: var(--border-radius-sm); transition: var(--transition-medium); cursor: pointer; font-size: var(--font-size-sm); @@ -139,24 +140,30 @@ .saveButton { flex: 2; background-color: var(--modal-save-bg); + + background-color: #245CF8; + background-color: #1745E8; 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); + border: 2px solid #0A3CC5; + border-radius: var(--border-radius-sm); 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); + background-color: #052FC8; + border: 2px solid #0B2FAF; } .saveButton:active { background-color: var(--modal-save-active-bg); + background-color: #072698; + border: 2px solid #092072; transform: translateY(1px); box-shadow: var(--modal-save-active-shadow); } diff --git a/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx b/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx index dc82332..4279c84 100644 --- a/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx +++ b/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx @@ -116,6 +116,7 @@ export function InlineModalExtendedBookingForm({ {/* Time Selection */}
+
+
+ {/* Actions */}
+ +
+
+ ); +} \ No newline at end of file diff --git a/my-app/src/components/forms/BookingTitleField.jsx b/my-app/src/components/forms/BookingTitleField.jsx index 2c517bf..efc1e3c 100644 --- a/my-app/src/components/forms/BookingTitleField.jsx +++ b/my-app/src/components/forms/BookingTitleField.jsx @@ -3,18 +3,20 @@ import { useBookingContext } from '../../context/BookingContext'; import { useSettingsContext } from '../../context/SettingsContext'; import styles from './BookingTitleField.module.css'; -export function BookingTitleField({ compact = false }) { +export function BookingTitleField({ compact = false, hideLabel = false }) { const booking = useBookingContext(); const { getDefaultBookingTitle } = useSettingsContext(); return ( <> -

Titel på bokning

+ {!hideLabel && ( +

Titel på bokning

+ )} booking.setTitle(event.target.value)} - placeholder={getDefaultBookingTitle()} + placeholder={hideLabel ? "Titel på bokning" : getDefaultBookingTitle()} className={compact ? styles.compactTextInput : styles.textInput} /> diff --git a/my-app/src/components/forms/ParticipantsSelector.jsx b/my-app/src/components/forms/ParticipantsSelector.jsx index fbfea5c..fdcd2a5 100644 --- a/my-app/src/components/forms/ParticipantsSelector.jsx +++ b/my-app/src/components/forms/ParticipantsSelector.jsx @@ -4,7 +4,7 @@ import { useBookingContext } from '../../context/BookingContext'; import { useSettingsContext } from '../../context/SettingsContext'; import styles from './ParticipantsSelector.module.css'; -export function ParticipantsSelector({ compact = false }) { +export function ParticipantsSelector({ compact = false, hideLabel = false }) { const booking = useBookingContext(); const { getCurrentUser } = useSettingsContext(); const [searchTerm, setSearchTerm] = useState(''); @@ -169,7 +169,9 @@ export function ParticipantsSelector({ compact = false }) { return (
-

Deltagare

+ {!hideLabel && ( +

Deltagare

+ )} {/* Search Input */}
@@ -182,7 +184,7 @@ export function ParticipantsSelector({ compact = false }) { onClick={handleInputClick} onBlur={handleInputBlur} onKeyDown={handleKeyDown} - placeholder="Sök deltagare..." + placeholder={hideLabel ? "Deltagare..." : "Sök deltagare..."} className={compact ? styles.compactSearchInput : styles.searchInput} role="combobox" aria-expanded={isDropdownOpen} diff --git a/my-app/src/components/forms/ParticipantsSelector.module.css b/my-app/src/components/forms/ParticipantsSelector.module.css index 0e66af1..6a6257c 100644 --- a/my-app/src/components/forms/ParticipantsSelector.module.css +++ b/my-app/src/components/forms/ParticipantsSelector.module.css @@ -305,6 +305,7 @@ font-family: inherit; transition: border-color 0.2s ease; box-sizing: border-box; + margin-bottom: 0.4rem; } .compactSearchInput:focus { @@ -313,3 +314,4 @@ border-color: var(--color-primary); } + diff --git a/my-app/src/components/ui/TimeCardContainer.jsx b/my-app/src/components/ui/TimeCardContainer.jsx index edf949c..316c915 100644 --- a/my-app/src/components/ui/TimeCardContainer.jsx +++ b/my-app/src/components/ui/TimeCardContainer.jsx @@ -4,6 +4,7 @@ import TimeCard from './TimeCard'; import { InlineBookingForm } from '../booking/InlineBookingForm'; import { InlineModalBookingForm } from '../booking/InlineModalBookingForm'; import { InlineModalExtendedBookingForm } from '../booking/InlineModalExtendedBookingForm'; +import { InlineModalExtendedBookingFormNoLabels } from '../booking/InlineModalExtendedBookingFormNoLabels'; import { BookingModal } from '../booking/BookingModal'; import styles from './TimeCardContainer.module.css'; import modalStyles from '../booking/BookingModal.module.css'; @@ -21,6 +22,7 @@ export function TimeCardContainer() { const useInlineForm = settings.bookingFormType === 'inline'; const useInlineModal = settings.bookingFormType === 'inline-modal'; const useInlineModalExtended = settings.bookingFormType === 'inline-modal-extended'; + const useInlineModalExtendedNoLabels = settings.bookingFormType === 'inline-modal-extended-no-labels'; const useModal = settings.bookingFormType === 'modal'; const handleNavigateToDetails = () => { @@ -145,7 +147,7 @@ export function TimeCardContainer() { // Add inline booking form after the pair that contains the selected time card // Cards are laid out in pairs: (0,1), (2,3), (4,5), etc. - if ((useInlineForm || useInlineModal || useInlineModalExtended) && booking.selectedStartIndex !== null) { + if ((useInlineForm || useInlineModal || useInlineModalExtended || useInlineModalExtendedNoLabels) && booking.selectedStartIndex !== null) { const selectedPairStart = Math.floor(booking.selectedStartIndex / 2) * 2; const selectedPairEnd = selectedPairStart + 1; @@ -189,6 +191,19 @@ export function TimeCardContainer() { arrowPointsLeft={isLeftCard} /> ); + } else if (useInlineModalExtendedNoLabels) { + elements.push( + booking.resetTimeSelections()} + onNavigateToDetails={handleNavigateToDetails} + arrowPointsLeft={isLeftCard} + /> + ); } } } diff --git a/my-app/src/context/SettingsContext.jsx b/my-app/src/context/SettingsContext.jsx index b287737..4cba687 100644 --- a/my-app/src/context/SettingsContext.jsx +++ b/my-app/src/context/SettingsContext.jsx @@ -32,6 +32,7 @@ export const SettingsProvider = ({ children }) => { showBookingConfirmationBanner: false, showBookingDeleteBanner: false, bookingFormType: 'inline', // 'modal' or 'inline' + showFiltersAlways: false, // Show filter dropdowns always or behind toggle button // Then override with saved values ...parsed, // Convert date strings back to DateValue objects @@ -67,6 +68,8 @@ export const SettingsProvider = ({ children }) => { showBookingDeleteBanner: false, // Booking form type bookingFormType: 'inline', // 'modal' or 'inline' + // Filter display mode + showFiltersAlways: false, // Show filter dropdowns always or behind toggle button }; }); @@ -111,6 +114,7 @@ export const SettingsProvider = ({ children }) => { showBookingConfirmationBanner: false, showBookingDeleteBanner: false, bookingFormType: 'inline', + showFiltersAlways: false, }); localStorage.removeItem('calendarSettings'); }; diff --git a/my-app/src/pages/BookingSettings.jsx b/my-app/src/pages/BookingSettings.jsx index 1c4adc2..73e2406 100644 --- a/my-app/src/pages/BookingSettings.jsx +++ b/my-app/src/pages/BookingSettings.jsx @@ -151,20 +151,49 @@ export function BookingSettings() { +
Current: {settings.bookingFormType === 'inline' ? 'Inline Form' : settings.bookingFormType === 'modal' ? 'Modal Popup' : settings.bookingFormType === 'inline-modal' ? 'Inline Modal' : - 'Inline Modal Extended'} + settings.bookingFormType === 'inline-modal-extended' ? 'Inline Modal Extended' : + settings.bookingFormType === 'inline-modal-extended-no-labels' ? 'Inline Modal Extended No Labels' : + 'Unknown'}
Inline Form: All fields in one form
Modal Popup: Time selection in popup, then details page
Inline Modal: Time selection inline, then details page
- Inline Modal Extended: Like hybrid, plus title and participants + Inline Modal Extended: Like hybrid, plus title and participants
+ Inline Modal Extended No Labels: Like extended hybrid, but without field labels +
+
+ +
+ +
+ updateSettings({ showFiltersAlways: e.target.checked })} + className={styles.toggle} + /> + + {settings.showFiltersAlways ? 'Always Show Dropdowns' : 'Show Filter Button'} + +
+
+ Always Show Dropdowns: Room and duration filters are always visible
+ Show Filter Button: Filters are hidden behind a collapsible button
diff --git a/my-app/src/pages/NewBooking.jsx b/my-app/src/pages/NewBooking.jsx index 089862c..0d8071a 100644 --- a/my-app/src/pages/NewBooking.jsx +++ b/my-app/src/pages/NewBooking.jsx @@ -66,40 +66,53 @@ export function NewBooking({ addBooking }) {
- {/* Filter Button */} + {/* Filter Section */}

Välj starttid

- - - {/* Collapsible Filter Content */} - {showFilters && ( + {settings.showFiltersAlways ? ( + /* Always-visible filters */
- {hasActiveFilters && ( -
- +
+ ) : ( + /* Toggle button with collapsible filters */ + <> + + + {/* Collapsible Filter Content */} + {showFilters && ( +
+
+ + +
+ {hasActiveFilters && ( +
+ +
+ )}
)} -
+ )}
diff --git a/my-app/src/pages/NewBooking.module.css b/my-app/src/pages/NewBooking.module.css index 98c1882..00b3227 100644 --- a/my-app/src/pages/NewBooking.module.css +++ b/my-app/src/pages/NewBooking.module.css @@ -37,10 +37,9 @@ .headerAndFilter { width: 100%; display: flex; - flex-direction: row; + flex-direction: column; align-items: center; justify-content: center; - gap: 11.5rem; padding-top: 2rem } -- 2.39.5 From c5820294f9f68cec0505b61f8142acfe93ae6afa Mon Sep 17 00:00:00 2001 From: Jacob Reinikainen <80760206+jazzjacob@users.noreply.github.com> Date: Fri, 12 Sep 2025 13:38:54 +0200 Subject: [PATCH 04/13] success screen fixes --- .../InlineModalExtendedBookingForm.jsx | 129 ++++++++++++++++-- .../components/forms/BookingLengthField.jsx | 3 +- .../components/forms/RoomSelectionField.jsx | 3 +- my-app/src/components/ui/Chip.jsx | 44 ++++++ my-app/src/components/ui/Chip.module.css | 88 ++++++++++++ my-app/src/components/ui/Dropdown.jsx | 4 +- my-app/src/components/ui/Dropdown.module.css | 6 + .../src/components/ui/TimeCardContainer.jsx | 3 +- my-app/src/pages/NewBooking.jsx | 8 +- my-app/src/pages/NewBooking.module.css | 10 +- 10 files changed, 274 insertions(+), 24 deletions(-) create mode 100644 my-app/src/components/ui/Chip.jsx create mode 100644 my-app/src/components/ui/Chip.module.css diff --git a/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx b/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx index 4279c84..7ab8db4 100644 --- a/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx +++ b/my-app/src/components/booking/InlineModalExtendedBookingForm.jsx @@ -1,15 +1,33 @@ import React, { useState, useEffect, useRef } from 'react'; import { Button } from 'react-aria-components'; import { useNavigate } from 'react-router-dom'; -import { convertDateObjectToString, getTimeFromIndex } from '../../helpers'; +import { convertDateObjectToString, getTimeFromIndex, formatBookingDate } from '../../helpers'; import Dropdown from '../ui/Dropdown'; +import { Chip } from '../ui/Chip'; import { BookingTitleField } from '../forms/BookingTitleField'; import { ParticipantsSelector } from '../forms/ParticipantsSelector'; import { useBookingContext } from '../../context/BookingContext'; import { useSettingsContext } from '../../context/SettingsContext'; +import { generateId } from '../../utils/bookingUtils'; +import { USER } from '../../constants/bookingConstants'; import styles from './InlineModalBookingForm.module.css'; import extendedStyles from './InlineModalExtendedBookingForm.module.css'; +// Helper function to get room category +function getRoomCategory(roomName) { + // Extract room number from room name (e.g., "G5:7" -> 7) + const roomNumber = parseInt(roomName.split(':')[1]); + + // Assign categories based on room number ranges + if (roomNumber >= 1 && roomNumber <= 4) return 'green'; + if (roomNumber >= 5 && roomNumber <= 8) return 'red'; + if (roomNumber >= 9 && roomNumber <= 12) return 'blue'; + if (roomNumber >= 13 && roomNumber <= 15) return 'yellow'; + + // Default fallback + return 'green'; +} + export function InlineModalExtendedBookingForm({ startTimeIndex, hoursAvailable, @@ -17,6 +35,7 @@ export function InlineModalExtendedBookingForm({ setEndTimeIndex, onClose, onNavigateToDetails, + addBooking, arrowPointsLeft = true }) { const navigate = useNavigate(); @@ -27,6 +46,7 @@ export function InlineModalExtendedBookingForm({ 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 [showConfirmation, setShowConfirmation] = useState(false); const hasInitialized = useRef(false); // Store the original hours available to prevent it from changing when selections are made @@ -43,6 +63,8 @@ export function InlineModalExtendedBookingForm({ booking.setSelectedEndIndex(initialEndTimeIndex); hasInitialized.current = true; } + + console.log("Booking:", booking); }, [initialEndTimeIndex, setEndTimeIndex, booking]); // Generate end time options based on available hours @@ -93,22 +115,101 @@ export function InlineModalExtendedBookingForm({ // Check if user has selected an end time (including pre-selected) const hasSelectedEndTime = selectedEndTimeIndex !== null; - const handleNavigateToConfirmation = () => { - if (hasSelectedEndTime) { - // Navigate directly to booking confirmation instead of details - navigate('/booking-confirmation', { - state: { - selectedDate: booking.selectedDate, - selectedStartIndex: booking.selectedStartIndex, - selectedEndIndex: booking.selectedEndIndex, - assignedRoom: booking.assignedRoom, - title: booking.title, - participants: booking.participants - } + const handleSave = () => { + if (hasSelectedEndTime && addBooking) { + console.log('Booking context state:', { + title: booking.title, + participants: booking.participants, + selectedRoom: booking.selectedRoom, + assignedRoom: booking.assignedRoom }); + + // Create a booking object with the same logic as in useBookingState + const roomToBook = booking.selectedRoom !== "allRooms" ? booking.selectedRoom : booking.assignedRoom; + + // Include the current user as a participant if not already added + const allParticipants = booking.participants.find(p => p.id === USER.id) + ? booking.participants + : [USER, ...booking.participants]; + + const finalTitle = booking.title !== "" ? booking.title : getDefaultBookingTitle(); + + const newBooking = { + id: generateId(), + date: booking.selectedDate, + startTime: booking.selectedStartIndex, + endTime: booking.selectedEndIndex, + room: roomToBook, + roomCategory: getRoomCategory(roomToBook), + title: finalTitle, + participants: allParticipants + }; + + console.log('Creating booking:', newBooking); + console.log('Final title used:', finalTitle); + + // Save the booking using the passed addBooking function + addBooking(newBooking); + + // Show confirmation page within the modal instead of navigating + setShowConfirmation(true); } }; + // Show confirmation page if user pressed save + if (showConfirmation) { + return ( +
+
+
+

Bokning sparad!

+

+ {booking.title ? `${booking.title}` : `${getDefaultBookingTitle()}`} +

+ {(() => { + // Include the current user as a participant if not already added + const allParticipants = booking.participants.find(p => p.id === USER.id) + ? booking.participants + : [USER, ...booking.participants]; + + const startTime = getTimeFromIndex(booking.selectedStartIndex); + const endTime = getTimeFromIndex(booking.selectedEndIndex); + const dateStr = formatBookingDate(booking.selectedDate); + const roomName = booking.selectedRoom !== "allRooms" ? booking.selectedRoom : booking.assignedRoom; + + return ( +
+

+ {dateStr} • {startTime} - {endTime} +

+
+ Rum: + + {roomName} + +
+ {allParticipants.length > 0 && ( +

+ Deltagare: {allParticipants.map(p => p.name).join(', ')} +

+ )} +
+ ); + })()} +
+
+ +
+ +
+ +
+
+ ); + } + return (
{/* Header */} @@ -151,7 +252,7 @@ export function InlineModalExtendedBookingForm({
); diff --git a/my-app/src/components/forms/RoomSelectionField.jsx b/my-app/src/components/forms/RoomSelectionField.jsx index 30923b9..8ab3ddc 100644 --- a/my-app/src/components/forms/RoomSelectionField.jsx +++ b/my-app/src/components/forms/RoomSelectionField.jsx @@ -4,7 +4,7 @@ import { useBookingContext } from '../../context/BookingContext'; import { useSettingsContext } from '../../context/SettingsContext'; import styles from './RoomSelectionField.module.css'; -export function RoomSelectionField() { +export function RoomSelectionField({ clean = false }) { const booking = useBookingContext(); const { settings } = useSettingsContext(); @@ -27,6 +27,7 @@ export function RoomSelectionField() { label: "Alla rum", value: "allRooms" }} + clean={clean} />
); diff --git a/my-app/src/components/ui/Chip.jsx b/my-app/src/components/ui/Chip.jsx new file mode 100644 index 0000000..80e2310 --- /dev/null +++ b/my-app/src/components/ui/Chip.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import styles from './Chip.module.css'; + +export function Chip({ + children, + onClick, + href, + variant = 'default', + size = 'medium', + className = '', + ...props +}) { + const baseClasses = `${styles.chip} ${styles[variant]} ${styles[size]} ${className}`; + + if (href) { + return ( + + {children} + + ); + } + + if (onClick) { + return ( + + ); + } + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/my-app/src/components/ui/Chip.module.css b/my-app/src/components/ui/Chip.module.css new file mode 100644 index 0000000..333838b --- /dev/null +++ b/my-app/src/components/ui/Chip.module.css @@ -0,0 +1,88 @@ +.chip { + display: inline-flex; + align-items: center; + border-radius: 12px; + font-weight: 500; + text-decoration: none; + border: none; + cursor: default; + transition: all 0.2s ease; + white-space: nowrap; + width: fit-content; +} + +/* Variants */ +.default { + background-color: #f3f4f6; + color: #374151; +} + +.primary { + background-color: #3b82f6; + color: white; +} + +.success { + background-color: #10b981; + color: white; +} + +.warning { + background-color: #f59e0b; + color: white; +} + +.error { + background-color: #ef4444; + color: white; +} + +.room { + background-color: #e0e7ff; + color: #3730a3; + border: 1px solid #c7d2fe; +} + +/* Sizes */ +.small { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; +} + +.medium { + padding: 0.375rem 0.75rem; + font-size: 0.875rem; +} + +.large { + padding: 0.5rem 1rem; + font-size: 1rem; +} + +/* Interactive states */ +.link, +.button { + cursor: pointer; +} + +.link:hover, +.button:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.link:active, +.button:active { + transform: translateY(0); +} + +.room.link:hover, +.room.button:hover { + background-color: #c7d2fe; + border-color: #a5b4fc; +} + +/* Remove button styling */ +.button { + font-family: inherit; +} \ No newline at end of file diff --git a/my-app/src/components/ui/Dropdown.jsx b/my-app/src/components/ui/Dropdown.jsx index 04fa872..c632683 100644 --- a/my-app/src/components/ui/Dropdown.jsx +++ b/my-app/src/components/ui/Dropdown.jsx @@ -3,13 +3,13 @@ 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, className }) => { +const Dropdown = ({ options, value, onChange, placeholder = {value: "", label: "Select an option"}, disabledOptions = false, className, clean = false }) => { return (