improving-week-36 #1
@@ -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>
|
||||
|
||||
73
my-app/src/components/BookingDeleteBanner.jsx
Normal file
73
my-app/src/components/BookingDeleteBanner.jsx
Normal 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;
|
||||
131
my-app/src/components/BookingDeleteBanner.module.css
Normal file
131
my-app/src/components/BookingDeleteBanner.module.css
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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 ? (
|
||||
<>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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'>
|
||||
|
||||
Reference in New Issue
Block a user