improving-week-36 #1

Merged
jare2473 merged 41 commits from improving-week-36 into main 2025-09-04 10:49:05 +02:00
7 changed files with 259 additions and 3 deletions
Showing only changes of commit bdadd66f21 - Show all commits

View File

@@ -13,6 +13,8 @@ const AppRoutes = () => {
const [loading, setLoading] = useState(false);
const [showSuccessBanner, setShowSuccessBanner] = useState(false);
const [lastCreatedBooking, setLastCreatedBooking] = useState(null);
const [showDeleteBanner, setShowDeleteBanner] = useState(false);
const [lastDeletedBooking, setLastDeletedBooking] = useState(null);
const [bookings, setBookings] = useState([
{
id: 1,
@@ -86,6 +88,8 @@ const AppRoutes = () => {
function deleteBooking(bookingToDelete) {
setBookings(bookings.filter(booking => booking.id !== bookingToDelete.id));
setLastDeletedBooking(bookingToDelete);
setShowDeleteBanner(true);
}
useEffect(() => {
@@ -102,7 +106,7 @@ const AppRoutes = () => {
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<RoomBooking bookings={bookings} showSuccessBanner={showSuccessBanner} lastCreatedBooking={lastCreatedBooking} onDismissBanner={() => setShowSuccessBanner(false)} onBookingUpdate={updateBooking} onBookingDelete={deleteBooking} />} />
<Route index element={<RoomBooking bookings={bookings} showSuccessBanner={showSuccessBanner} lastCreatedBooking={lastCreatedBooking} onDismissBanner={() => setShowSuccessBanner(false)} onBookingUpdate={updateBooking} onBookingDelete={deleteBooking} showDeleteBanner={showDeleteBanner} lastDeletedBooking={lastDeletedBooking} onDismissDeleteBanner={() => setShowDeleteBanner(false)} />} />
<Route path="new-booking" element={<NewBooking addBooking={addBooking} />} />
<Route path="booking-settings" element={<BookingSettings />} />
</Route>

View File

@@ -0,0 +1,73 @@
import React, { useState } from 'react';
import styles from './BookingDeleteBanner.module.css';
import { convertDateObjectToString } from '../helpers';
function BookingDeleteBanner({ booking, onClose, showFakeCloseButton = false, isTestBanner = false }) {
const [showTooltip, setShowTooltip] = useState(false);
function getTimeFromIndex(timeIndex) {
const totalHalfHoursFromStart = timeIndex;
const totalMinutes = 8 * 60 + totalHalfHoursFromStart * 30; // 8:00 as base
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${hours}:${minutes === 0 ? '00' : '30'}`;
}
function formatBookingDetails(booking) {
if (!booking) return '';
const dateStr = convertDateObjectToString(booking.date);
const startTime = getTimeFromIndex(booking.startTime);
const endTime = getTimeFromIndex(booking.endTime);
return `${booking.room}${dateStr}${startTime}-${endTime}`;
}
if (!booking) return null;
const handleFakeClose = () => {
setShowTooltip(true);
setTimeout(() => setShowTooltip(false), 3000);
};
return (
<div className={styles.deleteBanner}>
<div className={styles.bannerContent}>
<span className={styles.deleteIcon}>🗑</span>
<div className={styles.deleteText}>
<div className={styles.titleRow}>
<span className={styles.deleteTitle}>Bokning raderad: {booking.title && <span className={styles.bookingTitle}>{booking.title}</span>}</span>
{isTestBanner && <span className={styles.testLabel}>TEST</span>}
</div>
<span className={styles.deleteDetails}>{formatBookingDetails(booking)}</span>
</div>
</div>
{!showFakeCloseButton && (
<button
className={styles.bannerCloseButton}
onClick={onClose}
>
×
</button>
)}
{showFakeCloseButton && (
<div className={styles.fakeCloseContainer}>
<button
className={styles.bannerCloseButton}
onClick={handleFakeClose}
>
×
</button>
{showTooltip && (
<div className={styles.tooltip}>
Detta är en testbanner som inte kan stängas
</div>
)}
</div>
)}
</div>
);
}
export default BookingDeleteBanner;

View File

@@ -0,0 +1,131 @@
.deleteBanner {
background: #FFF4F4;
border: 1px solid #F87171;
border-radius: 0.75rem;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 8px rgba(248, 113, 113, 0.15);
}
.bannerContent {
display: flex;
align-items: center;
gap: 1rem;
}
.deleteIcon {
background: #EF4444;
color: white;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1rem;
flex-shrink: 0;
}
.deleteText {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.titleRow {
display: flex;
align-items: center;
gap: 0.5rem;
}
.deleteTitle {
color: #DC2626;
font-weight: 700;
font-size: 1.1rem;
}
.deleteDetails {
color: #EF4444;
font-weight: 500;
font-size: 0.9rem;
}
.bannerCloseButton {
background: none;
border: none;
color: #6C757D;
font-size: 1.5rem;
font-weight: 300;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
transition: all 0.2s ease;
line-height: 1;
flex-shrink: 0;
width: fit-content;
}
.bannerCloseButton:hover {
background: rgba(108, 117, 125, 0.1);
color: #495057;
}
.bookingTitle {
font-weight: 400;
}
.testLabel {
background: #FF9800;
color: white;
font-size: 0.7rem;
font-weight: 700;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.fakeCloseContainer {
position: relative;
}
.tooltip {
position: absolute;
top: 100%;
right: 0;
margin-top: 0.5rem;
background: #333;
color: white;
padding: 0.75rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
white-space: nowrap;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10;
animation: fadeIn 0.2s ease-out;
}
.tooltip::before {
content: '';
position: absolute;
bottom: 100%;
right: 1rem;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #333;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@@ -3,8 +3,9 @@ import { CalendarDate } from '@internationalized/date';
import styles from './BookingsList.module.css';
import BookingCard from './BookingCard';
import BookingConfirmationBanner from './BookingConfirmationBanner';
import BookingDeleteBanner from './BookingDeleteBanner';
function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingDelete, showSuccessBanner, lastCreatedBooking, onDismissBanner, showDevelopmentBanner, showBookingConfirmationBanner }) {
function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingDelete, showSuccessBanner, lastCreatedBooking, onDismissBanner, showDeleteBanner, lastDeletedBooking, onDismissDeleteBanner, showDevelopmentBanner, showBookingConfirmationBanner, showBookingDeleteBanner }) {
const [showAll, setShowAll] = useState(false);
const [expandedBookingId, setExpandedBookingId] = useState(null);
const INITIAL_DISPLAY_COUNT = 3;
@@ -25,6 +26,12 @@ function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingD
showCloseButton={true}
/>
)}
{showDeleteBanner && (
<BookingDeleteBanner
booking={lastDeletedBooking}
onClose={onDismissDeleteBanner}
/>
)}
{showDevelopmentBanner && (
<div className={styles.developmentBanner}>
<div className={styles.bannerContent}>
@@ -46,6 +53,19 @@ function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingD
isTestBanner={true}
/>
)}
{showBookingDeleteBanner && (
<BookingDeleteBanner
booking={{
title: 'Uppföljningsmöte',
room: 'G5:12',
date: new CalendarDate(2025, 9, 5),
startTime: 6,
endTime: 8
}}
showFakeCloseButton={true}
isTestBanner={true}
/>
)}
<div className={styles.bookingsContainer}>
{bookings.length > 0 ? (
<>

View File

@@ -30,6 +30,7 @@ export const SettingsProvider = ({ children }) => {
currentUserName: USER.name,
showDevelopmentBanner: false,
showBookingConfirmationBanner: false,
showBookingDeleteBanner: false,
// Then override with saved values
...parsed,
// Convert date strings back to DateValue objects
@@ -61,6 +62,8 @@ export const SettingsProvider = ({ children }) => {
showDevelopmentBanner: false,
// Booking confirmation banner toggle
showBookingConfirmationBanner: false,
// Booking delete banner toggle
showBookingDeleteBanner: false,
};
});

View File

@@ -107,6 +107,27 @@ export function BookingSettings() {
</span>
</div>
</div>
<div className={styles.setting}>
<label htmlFor="bookingDeleteBanner">
<strong>Show Booking Delete Banner</strong>
<span className={styles.description}>
Display a banner that looks like a booking deletion confirmation
</span>
</label>
<div className={styles.toggleGroup}>
<input
id="bookingDeleteBanner"
type="checkbox"
checked={settings.showBookingDeleteBanner}
onChange={(e) => updateSettings({ showBookingDeleteBanner: e.target.checked })}
className={styles.toggle}
/>
<span className={styles.toggleStatus}>
{settings.showBookingDeleteBanner ? 'Enabled' : 'Disabled'}
</span>
</div>
</div>
</div>
<div className={styles.section}>

View File

@@ -5,7 +5,7 @@ import BookingsList from '../components/BookingsList';
import Card from '../components/Card';
import { useSettingsContext } from '../context/SettingsContext';
export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, onDismissBanner, onBookingUpdate, onBookingDelete }) {
export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, onDismissBanner, onBookingUpdate, onBookingDelete, showDeleteBanner, lastDeletedBooking, onDismissDeleteBanner }) {
const { settings } = useSettingsContext();
function handleEditBooking(booking) {
@@ -25,8 +25,12 @@ export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, o
showSuccessBanner={showSuccessBanner}
lastCreatedBooking={lastCreatedBooking}
onDismissBanner={onDismissBanner}
showDeleteBanner={showDeleteBanner}
lastDeletedBooking={lastDeletedBooking}
onDismissDeleteBanner={onDismissDeleteBanner}
showDevelopmentBanner={settings.showDevelopmentBanner}
showBookingConfirmationBanner={settings.showBookingConfirmationBanner}
showBookingDeleteBanner={settings.showBookingDeleteBanner}
/>
<h2>Ny bokning</h2>
<Link to='/new-booking'>