improving-week-36 #1
@@ -1,75 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import styles from './BookingConfirmationBanner.module.css';
|
||||
import { convertDateObjectToString } from '../helpers';
|
||||
|
||||
// Test change again
|
||||
|
||||
function BookingConfirmationBanner({ booking, onClose, showCloseButton = false, 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.confirmationBanner}>
|
||||
<div className={styles.bannerContent}>
|
||||
<span className={styles.confirmationIcon}>✓</span>
|
||||
<div className={styles.confirmationText}>
|
||||
<div className={styles.titleRow}>
|
||||
<span className={styles.confirmationTitle}>Bokning bekräftad: {booking.title && <span className={styles.bookingTitle}>{booking.title}</span>}</span>
|
||||
{isTestBanner && <span className={styles.testLabel}>TEST</span>}
|
||||
</div>
|
||||
<span className={styles.confirmationDetails}>{formatBookingDetails(booking)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{showCloseButton && (
|
||||
<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 BookingConfirmationBanner;
|
||||
@@ -1,131 +0,0 @@
|
||||
.confirmationBanner {
|
||||
background: #E8F5E8;
|
||||
border: 1px solid #4CAF50;
|
||||
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(76, 175, 80, 0.15);
|
||||
}
|
||||
|
||||
.bannerContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.confirmationIcon {
|
||||
background: #4CAF50;
|
||||
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;
|
||||
}
|
||||
|
||||
.confirmationText {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.confirmationTitle {
|
||||
color: #2E7D32;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.confirmationDetails {
|
||||
color: #388E3C;
|
||||
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;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
.bookingTitle {
|
||||
font-weight: 400;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
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;
|
||||
@@ -2,8 +2,7 @@ import React, { useState } from 'react';
|
||||
import { CalendarDate } from '@internationalized/date';
|
||||
import styles from './BookingsList.module.css';
|
||||
import BookingCard from './BookingCard';
|
||||
import BookingConfirmationBanner from './BookingConfirmationBanner';
|
||||
import BookingDeleteBanner from './BookingDeleteBanner';
|
||||
import NotificationBanner from './NotificationBanner';
|
||||
|
||||
function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingDelete, showSuccessBanner, lastCreatedBooking, onDismissBanner, showDeleteBanner, lastDeletedBooking, onDismissDeleteBanner, showDevelopmentBanner, showBookingConfirmationBanner, showBookingDeleteBanner }) {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
@@ -20,28 +19,29 @@ function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingD
|
||||
return (
|
||||
<div className={styles.bookingsListContainer}>
|
||||
{showSuccessBanner && (
|
||||
<BookingConfirmationBanner
|
||||
<NotificationBanner
|
||||
variant="success"
|
||||
booking={lastCreatedBooking}
|
||||
onClose={onDismissBanner}
|
||||
showCloseButton={true}
|
||||
/>
|
||||
)}
|
||||
{showDeleteBanner && (
|
||||
<BookingDeleteBanner
|
||||
<NotificationBanner
|
||||
variant="delete"
|
||||
booking={lastDeletedBooking}
|
||||
onClose={onDismissDeleteBanner}
|
||||
showCloseButton={true}
|
||||
/>
|
||||
)}
|
||||
{showDevelopmentBanner && (
|
||||
<div className={styles.developmentBanner}>
|
||||
<div className={styles.bannerContent}>
|
||||
<span className={styles.developmentIcon}>🔧</span>
|
||||
<span>Visar testdata för utveckling</span>
|
||||
</div>
|
||||
</div>
|
||||
<NotificationBanner
|
||||
variant="development"
|
||||
/>
|
||||
)}
|
||||
{showBookingConfirmationBanner && (
|
||||
<BookingConfirmationBanner
|
||||
<NotificationBanner
|
||||
variant="success"
|
||||
booking={{
|
||||
title: 'Projektmöte',
|
||||
room: 'G5:7',
|
||||
@@ -54,7 +54,8 @@ function BookingsList({ bookings, handleEditBooking, onBookingUpdate, onBookingD
|
||||
/>
|
||||
)}
|
||||
{showBookingDeleteBanner && (
|
||||
<BookingDeleteBanner
|
||||
<NotificationBanner
|
||||
variant="delete"
|
||||
booking={{
|
||||
title: 'Uppföljningsmöte',
|
||||
room: 'G5:12',
|
||||
|
||||
@@ -54,35 +54,6 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.bannerContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.developmentBanner {
|
||||
background: #FFF8E1;
|
||||
border: 1px solid #FFB74D;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 8px rgba(255, 183, 77, 0.15);
|
||||
}
|
||||
|
||||
.developmentIcon {
|
||||
color: #F57F17;
|
||||
font-size: 1.25rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.developmentBanner .bannerContent span:last-child {
|
||||
color: #E65100;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
@keyframes slideDown {
|
||||
|
||||
129
my-app/src/components/NotificationBanner.jsx
Normal file
129
my-app/src/components/NotificationBanner.jsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React, { useState } from 'react';
|
||||
import styles from './NotificationBanner.module.css';
|
||||
import { convertDateObjectToString } from '../helpers';
|
||||
|
||||
const BANNER_VARIANTS = {
|
||||
success: {
|
||||
icon: '✓',
|
||||
title: 'Bokning bekräftad:',
|
||||
className: 'success'
|
||||
},
|
||||
delete: {
|
||||
icon: '🗑️',
|
||||
title: 'Bokning raderad:',
|
||||
className: 'delete'
|
||||
},
|
||||
development: {
|
||||
icon: '🔧',
|
||||
title: 'Visar testdata för utveckling',
|
||||
className: 'development'
|
||||
}
|
||||
};
|
||||
|
||||
function NotificationBanner({
|
||||
variant = 'success',
|
||||
booking = null,
|
||||
onClose,
|
||||
showCloseButton = false,
|
||||
showFakeCloseButton = false,
|
||||
isTestBanner = false,
|
||||
customTitle = null,
|
||||
customContent = null
|
||||
}) {
|
||||
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}`;
|
||||
}
|
||||
|
||||
const handleFakeClose = () => {
|
||||
setShowTooltip(true);
|
||||
setTimeout(() => setShowTooltip(false), 3000);
|
||||
};
|
||||
|
||||
const config = BANNER_VARIANTS[variant] || BANNER_VARIANTS.success;
|
||||
|
||||
// For development banner, use custom content
|
||||
if (variant === 'development') {
|
||||
return (
|
||||
<div className={`${styles.banner} ${styles[config.className]}`}>
|
||||
<div className={styles.bannerContent}>
|
||||
<span className={styles.developmentIcon}>{config.icon}</span>
|
||||
<span>{customTitle || config.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// For booking-related banners (success/delete)
|
||||
if (!booking && !customContent) return null;
|
||||
|
||||
return (
|
||||
<div className={`${styles.banner} ${styles[config.className]}`}>
|
||||
<div className={styles.bannerContent}>
|
||||
<span className={`${styles.icon} ${styles[config.className + 'Icon']}`}>
|
||||
{config.icon}
|
||||
</span>
|
||||
<div className={styles.text}>
|
||||
<div className={styles.titleRow}>
|
||||
<span className={`${styles.title} ${styles[config.className + 'Title']}`}>
|
||||
{customTitle || config.title} {booking && booking.title && <span className={styles.bookingTitle}>{booking.title}</span>}
|
||||
</span>
|
||||
{isTestBanner && <span className={styles.testLabel}>TEST</span>}
|
||||
</div>
|
||||
{booking && (
|
||||
<span className={`${styles.details} ${styles[config.className + 'Details']}`}>
|
||||
{formatBookingDetails(booking)}
|
||||
</span>
|
||||
)}
|
||||
{customContent && (
|
||||
<span className={`${styles.details} ${styles[config.className + 'Details']}`}>
|
||||
{customContent}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
className={styles.closeButton}
|
||||
onClick={onClose}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
{showFakeCloseButton && (
|
||||
<div className={styles.fakeCloseContainer}>
|
||||
<button
|
||||
className={styles.closeButton}
|
||||
onClick={handleFakeClose}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
{showTooltip && (
|
||||
<div className={styles.tooltip}>
|
||||
Detta är en testbanner som inte kan stängas
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotificationBanner;
|
||||
@@ -1,13 +1,12 @@
|
||||
.deleteBanner {
|
||||
background: #FFF4F4;
|
||||
border: 1px solid #F87171;
|
||||
/* Base banner styles */
|
||||
.banner {
|
||||
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);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.bannerContent {
|
||||
@@ -16,9 +15,7 @@
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.deleteIcon {
|
||||
background: #EF4444;
|
||||
color: white;
|
||||
.icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
@@ -30,7 +27,7 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.deleteText {
|
||||
.text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
@@ -42,19 +39,21 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.deleteTitle {
|
||||
color: #DC2626;
|
||||
.title {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.deleteDetails {
|
||||
color: #EF4444;
|
||||
.details {
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.bannerCloseButton {
|
||||
.bookingTitle {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #6C757D;
|
||||
@@ -69,15 +68,61 @@
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.bannerCloseButton:hover {
|
||||
.closeButton:hover {
|
||||
background: rgba(108, 117, 125, 0.1);
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.bookingTitle {
|
||||
font-weight: 400;
|
||||
/* Success variant styles */
|
||||
.success {
|
||||
background: #E8F5E8;
|
||||
border: 1px solid #4CAF50;
|
||||
}
|
||||
|
||||
.successIcon {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.successTitle {
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.successDetails {
|
||||
color: #388E3C;
|
||||
}
|
||||
|
||||
/* Delete variant styles */
|
||||
.delete {
|
||||
background: #FFF4F4;
|
||||
border: 1px solid #F87171;
|
||||
}
|
||||
|
||||
.deleteIcon {
|
||||
background: #EF4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.deleteTitle {
|
||||
color: #DC2626;
|
||||
}
|
||||
|
||||
.deleteDetails {
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
/* Development variant styles */
|
||||
.development {
|
||||
background: #FFF8E1;
|
||||
border: 1px solid #FFB74D;
|
||||
}
|
||||
|
||||
.developmentIcon {
|
||||
font-size: 1.5rem;
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
/* Test label styles */
|
||||
.testLabel {
|
||||
background: #FF9800;
|
||||
color: white;
|
||||
@@ -89,6 +134,7 @@
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Fake close and tooltip styles */
|
||||
.fakeCloseContainer {
|
||||
position: relative;
|
||||
}
|
||||
Reference in New Issue
Block a user