booking-flow-finalized-design kindaaaa #7

Merged
jare2473 merged 20 commits from booking-flow-finalized-design into main 2025-09-30 10:50:54 +02:00
4 changed files with 489 additions and 0 deletions
Showing only changes of commit efd62e0733 - Show all commits

2
my-app/.gitignore vendored
View File

@@ -29,3 +29,5 @@ storybook-static
# Font files
public/caecilia/
public/the-sans/
deploy.sh

View File

@@ -14,6 +14,7 @@ import { TestSession } from './pages/TestSession';
import Home from './pages/Home';
import CoursePage from './pages/CoursePage';
import Profile from './pages/Profile';
import RoomSchedules from './pages/RoomSchedules';
const AppRoutes = () => {
const location = useLocation();
@@ -129,6 +130,7 @@ const AppRoutes = () => {
<Route path="home" element={<Home />} />
<Route path="course/:courseId" element={<CoursePage />} />
<Route path="profile" element={<Profile />} />
<Route path="room-schedules" element={<RoomSchedules />} />
</Route>
</Routes>
</>

View File

@@ -0,0 +1,236 @@
import React, { useState } from 'react';
import styles from './RoomSchedules.module.css';
const RoomSchedules = () => {
const [selectedBooking, setSelectedBooking] = useState(null);
const [showModal, setShowModal] = useState(false);
const timeSlots = [
'08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00',
'15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00',
'22:00', '23:00', '00:00'
];
const rooms = [
{
name: 'Lilla hörsalen',
bookings: [
{
id: 1,
course: 'MDI S2 grp 7',
time: '08:00 - 08:30',
instructor: 'Anna Andersson',
participants: 12,
description: 'Medicinsk datavetenskap seminarium'
},
{
id: 2,
course: 'MDI S2 grp 7',
time: '08:30 - 09:00',
instructor: 'Anna Andersson',
participants: 12,
description: 'Medicinsk datavetenskap seminarium'
},
{
id: 3,
course: 'INTROPROG F12',
time: '09:00 - 09:30',
instructor: 'Erik Larsson',
participants: 25,
description: 'Introduktion till programmering - föreläsning'
}
]
},
{
name: 'Aula NOD',
bookings: [
{
id: 4,
course: 'MDI S2 grp 7',
time: '08:00 - 08:30',
instructor: 'Anna Andersson',
participants: 12,
description: 'Medicinsk datavetenskap seminarium'
},
{
id: 5,
course: 'MDI S2 grp 7',
time: '08:30 - 09:00',
instructor: 'Anna Andersson',
participants: 12,
description: 'Medicinsk datavetenskap seminarium'
},
{
id: 6,
course: 'INTROPROG F12',
time: '09:00 - 09:30',
instructor: 'Erik Larsson',
participants: 25,
description: 'Introduktion till programmering - föreläsning'
},
{
id: 7,
course: 'DB L6 grp 2',
time: '10:00 - 12:45',
instructor: 'Sofia Karlsson',
participants: 18,
description: 'Databaser - laboration 6'
},
{
id: 8,
course: 'INTROPROG F12',
time: '13:00 - 14:00',
instructor: 'Erik Larsson',
participants: 25,
description: 'Introduktion till programmering - föreläsning'
}
]
},
{
name: 'G5:7',
bookings: [
{
id: 9,
course: 'Team standup',
time: '10:00 - 12:00',
instructor: 'Magnus Nilsson',
participants: 5,
description: 'Daglig standup för utvecklingsteam'
}
]
},
{
name: 'G5:12',
bookings: [
{
id: 10,
course: 'Project planning',
time: '16:00 - 20:00',
instructor: 'Emma Johansson',
participants: 8,
description: 'Projektplanering för kommande sprint'
}
]
}
];
const getBookingForTimeSlot = (room, timeSlot) => {
return room.bookings.find(booking => {
const [startTime] = booking.time.split(' - ');
return startTime === timeSlot;
});
};
const getBookingSpan = (booking) => {
const [startTime, endTime] = booking.time.split(' - ');
const startHour = parseInt(startTime.split(':')[0]);
const endHour = parseInt(endTime.split(':')[0]);
const startMinutes = parseInt(startTime.split(':')[1]);
const endMinutes = parseInt(endTime.split(':')[1]);
const startTotalMinutes = startHour * 60 + startMinutes;
const endTotalMinutes = endHour * 60 + endMinutes;
const durationMinutes = endTotalMinutes - startTotalMinutes;
return Math.max(1, Math.round(durationMinutes / 60));
};
const handleBookingClick = (booking, roomName) => {
setSelectedBooking({ ...booking, room: roomName });
setShowModal(true);
};
const closeModal = () => {
setShowModal(false);
setSelectedBooking(null);
};
return (
<div className={styles.RoomSchedulesContainer}>
<h1>Room Schedules</h1>
<div className={styles.ScheduleWrapper}>
<div className={styles.TimeColumn}>
<div className={styles.TimeHeader}></div>
{timeSlots.map((time, index) => (
<div key={index} className={styles.TimeSlot}>
{time}
</div>
))}
</div>
<div className={styles.RoomsContainer}>
{rooms.map((room, roomIndex) => (
<div key={roomIndex} className={styles.RoomColumn}>
<div className={styles.RoomHeader}>
{room.name}
</div>
{timeSlots.map((timeSlot, timeIndex) => {
const booking = getBookingForTimeSlot(room, timeSlot);
const isOccupied = booking !== undefined;
const span = booking ? getBookingSpan(booking) : 1;
return (
<div
key={timeIndex}
className={`${styles.ScheduleSlot} ${isOccupied ? styles.Occupied : ''}`}
style={{
gridRowEnd: isOccupied ? `span ${span}` : 'span 1'
}}
>
{isOccupied && (
<div
className={styles.BookingInfo}
onClick={() => handleBookingClick(booking, room.name)}
>
<div className={styles.CourseName}>{booking.course}</div>
<div className={styles.BookingTime}>{booking.time}</div>
</div>
)}
</div>
);
})}
</div>
))}
</div>
</div>
{showModal && selectedBooking && (
<div className={styles.ModalOverlay} onClick={closeModal}>
<div className={styles.Modal} onClick={(e) => e.stopPropagation()}>
<div className={styles.ModalHeader}>
<h2>{selectedBooking.course}</h2>
<button className={styles.CloseButton} onClick={closeModal}>
×
</button>
</div>
<div className={styles.ModalContent}>
<div className={styles.DetailRow}>
<span className={styles.Label}>Room:</span>
<span>{selectedBooking.room}</span>
</div>
<div className={styles.DetailRow}>
<span className={styles.Label}>Time:</span>
<span>{selectedBooking.time}</span>
</div>
<div className={styles.DetailRow}>
<span className={styles.Label}>Instructor:</span>
<span>{selectedBooking.instructor}</span>
</div>
<div className={styles.DetailRow}>
<span className={styles.Label}>Participants:</span>
<span>{selectedBooking.participants}</span>
</div>
<div className={styles.DetailRow}>
<span className={styles.Label}>Description:</span>
<span>{selectedBooking.description}</span>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default RoomSchedules;

View File

@@ -0,0 +1,249 @@
.RoomSchedulesContainer {
padding: 1rem;
}
.RoomSchedulesContainer h1 {
margin-bottom: 1rem;
}
.ScheduleWrapper {
display: flex;
overflow-x: auto;
border: 1px solid #e0e0e0;
border-radius: 8px;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-height: 80vh;
overflow-y: auto;
}
.TimeColumn {
flex-shrink: 0;
width: 80px;
border-right: 1px solid #e0e0e0;
background: #f8f9fa;
}
.TimeHeader {
height: 60px;
border-bottom: 1px solid #e0e0e0;
background: #f0f0f0;
position: sticky;
top: 0;
z-index: 15;
}
.TimeSlot {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #e0e0e0;
font-size: 0.9rem;
font-weight: 500;
color: #666;
}
.RoomsContainer {
display: flex;
flex: 1;
min-width: 0;
}
.RoomColumn {
flex-shrink: 0;
width: 200px;
border-right: 1px solid #e0e0e0;
display: grid;
grid-template-rows: 60px repeat(17, 60px);
}
.RoomColumn:last-child {
border-right: none;
}
.RoomHeader {
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #e0e0e0;
background: #f0f0f0;
font-weight: 600;
font-size: 1rem;
text-align: center;
padding: 0 1rem;
position: sticky;
top: 0;
z-index: 10;
}
.ScheduleSlot {
border-bottom: 1px solid #e0e0e0;
background: #f9f9f9;
position: relative;
}
.ScheduleSlot.Occupied {
background: #e3f2fd;
border: 1px solid #2196f3;
margin: 1px;
border-radius: 4px;
}
.BookingInfo {
padding: 8px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.BookingInfo:hover {
background: rgba(33, 150, 243, 0.1);
transform: scale(1.02);
}
.CourseName {
font-weight: 600;
font-size: 0.9rem;
color: #1976d2;
margin-bottom: 4px;
line-height: 1.2;
}
.BookingTime {
font-size: 0.8rem;
color: #666;
font-weight: 500;
}
/* Modal styles */
.ModalOverlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.Modal {
background: blue;
border-radius: 8px;
padding: 0;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.ModalHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid #e0e0e0;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
}
.ModalHeader h2 {
margin: 0;
font-size: 1.25rem;
color: #1976d2;
}
.CloseButton {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.2s ease;
}
.CloseButton:hover {
background: #e0e0e0;
}
.ModalContent {
padding: 1.5rem;
}
.DetailRow {
display: flex;
margin-bottom: 1rem;
align-items: flex-start;
}
.DetailRow:last-child {
margin-bottom: 0;
}
.Label {
font-weight: 600;
min-width: 100px;
color: #666;
margin-right: 1rem;
}
/* Mobile scrolling */
@media (max-width: 768px) {
.ScheduleWrapper {
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
}
.RoomColumn {
width: 180px;
}
.TimeColumn {
width: 70px;
}
.RoomHeader {
font-size: 0.9rem;
padding: 0 0.5rem;
}
.CourseName {
font-size: 0.8rem;
}
.BookingTime {
font-size: 0.7rem;
}
.Modal {
width: 95%;
margin: 1rem;
}
.ModalHeader {
padding: 1rem;
}
.ModalContent {
padding: 1rem;
}
.Label {
min-width: 80px;
font-size: 0.9rem;
}
}