improving-week-36 #1

Merged
jare2473 merged 41 commits from improving-week-36 into main 2025-09-04 10:49:05 +02:00
5 changed files with 327 additions and 12 deletions
Showing only changes of commit 090bb5c830 - Show all commits

View File

@@ -0,0 +1,118 @@
import React, { useState, useRef, useEffect } from 'react';
import { PEOPLE } from '../constants/bookingConstants';
import { useBookingContext } from '../context/BookingContext';
import styles from './ParticipantsSelector.module.css';
export function ParticipantsSelector() {
const booking = useBookingContext();
const [searchTerm, setSearchTerm] = useState('');
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [recentSearches, setRecentSearches] = useState([]);
const inputRef = useRef(null);
const dropdownRef = useRef(null);
// Filter people based on search term
const filteredPeople = PEOPLE.filter(person =>
person.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
person.email.toLowerCase().includes(searchTerm.toLowerCase())
);
// Show all people when empty search, filtered when typing
const displayPeople = searchTerm === '' ? PEOPLE : filteredPeople;
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleInputFocus = () => {
setIsDropdownOpen(true);
};
const handleInputChange = (e) => {
setSearchTerm(e.target.value);
setIsDropdownOpen(true);
};
const handleSelectPerson = (person) => {
console.log('handleSelectPerson called with:', person);
booking.handleParticipantChange(person.id);
setSearchTerm('');
setIsDropdownOpen(false);
inputRef.current?.blur();
};
const handleRemoveParticipant = (participantToRemove) => {
booking.handleRemoveParticipant(participantToRemove);
};
return (
<div className={styles.container}>
<h3 className={styles.elementHeading}>Deltagare</h3>
{/* Search Input */}
<div className={styles.searchContainer} ref={dropdownRef}>
<input
ref={inputRef}
type="text"
value={searchTerm}
onChange={handleInputChange}
onFocus={handleInputFocus}
placeholder="Search for participants..."
className={styles.searchInput}
/>
{/* Dropdown */}
{isDropdownOpen && (
<div className={styles.dropdown}>
{displayPeople.length > 0 ? (
<div className={styles.section}>
{displayPeople.map((person) => (
<div
key={person.id}
className={styles.dropdownItem}
onClick={() => handleSelectPerson(person)}
>
<div className={styles.personInfo}>
<div className={styles.personName}>{person.name}</div>
<div className={styles.personEmail}>{person.email}</div>
</div>
</div>
))}
</div>
) : (
<div className={styles.noResults}>
No participants found
</div>
)}
</div>
)}
</div>
{/* Selected Participants */}
{booking.participants.length > 0 && (
<div className={styles.selectedParticipants}>
{booking.participants.map((participant, index) => (
<div key={index} className={styles.participantChip}>
<span className={styles.participantName}>{participant}</span>
<button
className={styles.removeButton}
onClick={() => handleRemoveParticipant(participant)}
type="button"
title="Remove participant"
>
×
</button>
</div>
))}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,182 @@
.container {
position: relative;
}
.elementHeading {
margin: 0;
color: #8E8E8E;
font-size: 0.8rem;
font-style: normal;
font-weight: 520;
line-height: normal;
margin-bottom: 0.2rem;
margin-top: 1.5rem;
}
.selectedParticipants {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
padding: 0;
}
.participantChip {
display: flex;
align-items: center;
background-color: #F0F8FF;
border: 1px solid #D1E7FF;
border-radius: 1.25rem;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
color: #2563EB;
gap: 0.5rem;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.participantChip:hover {
background-color: #E0F2FE;
border-color: #BAE6FD;
}
.participantName {
font-weight: 500;
}
.removeButton {
background: rgba(37, 99, 235, 0.1);
border: none;
color: #2563EB;
cursor: pointer;
font-size: 0.875rem;
line-height: 1;
padding: 0;
width: 1.25rem;
height: 1.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
}
.removeButton:hover {
background-color: rgba(37, 99, 235, 0.2);
transform: scale(1.1);
}
.searchContainer {
position: relative;
width: 100%;
max-width: 600px;
}
.searchInput {
width: 100%;
margin-bottom: 10px;
border: 1px solid #D2D9E0;
border-radius: 0.5rem;
font-size: 16px;
background-color: #FAFBFC;
padding: 1rem;
font-family: inherit;
box-sizing: border-box;
}
.searchInput::placeholder {
color: #adadad;
}
.searchInput:focus {
outline: 2px solid var(--focus-ring-color, #3e70ec);
outline-offset: -1px;
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #D2D9E0;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
z-index: 1000;
max-height: 300px;
overflow-y: auto;
margin-top: -10px;
}
.section {
padding: 0.5rem 0;
}
.section:not(:last-child) {
border-bottom: 1px solid #F1F3F4;
}
.sectionHeader {
font-weight: 600;
color: #5F6368;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 0.25rem 1rem;
margin-bottom: 0.25rem;
}
.dropdownItem {
padding: 0.75rem 1rem;
cursor: pointer;
transition: background-color 0.2s;
border: none;
background: none;
width: 100%;
text-align: left;
font-family: inherit;
}
.dropdownItem:hover {
background-color: #F8F9FA;
}
.dropdownItem:active {
background-color: #E8F0FE;
}
.personInfo {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.personName {
font-weight: 500;
color: #202124;
font-size: 0.875rem;
}
.personEmail {
font-size: 0.75rem;
color: #5F6368;
}
.addNewItem {
color: #1A73E8;
font-weight: 500;
opacity: 0.5;
cursor: not-allowed;
}
.addNewItem:hover {
background-color: transparent;
}
.noResults {
padding: 1rem;
text-align: center;
color: #5F6368;
font-size: 0.875rem;
font-style: italic;
}

View File

@@ -19,12 +19,12 @@ export const SMALL_GROUP_ROOMS = Array.from({ length: 15 }, (_, i) => ({
}));
export const PEOPLE = [
{ id: 1, name: 'Arjohn Emilsson' },
{ id: 2, name: 'Filip Norgren' },
{ id: 3, name: 'Hedvig Engelmark' },
{ id: 4, name: 'Elin Rudling' },
{ id: 5, name: 'Victor Magnusson' },
{ id: 6, name: 'Ellen Britschgi' }
{ id: 1, name: 'Arjohn Emilsson', email: 'arjohn.emilsson@dsv.su.se' },
{ id: 2, name: 'Filip Norgren', email: 'filip.norgren@dsv.su.se' },
{ id: 3, name: 'Hedvig Engelmark', email: 'hedvig.engelmark@dsv.su.se' },
{ id: 4, name: 'Elin Rudling', email: 'elin.rudling@dsv.su.se' },
{ id: 5, name: 'Victor Magnusson', email: 'victor.magnusson@dsv.su.se' },
{ id: 6, name: 'Ellen Britschgi', email: 'ellen.britschgi@dsv.su.se' }
];
export const DEFAULT_DISABLED_OPTIONS = {

View File

@@ -6,7 +6,7 @@ import {
generateId,
findObjectById
} from '../utils/bookingUtils';
import { DEFAULT_BOOKING_TITLE } from '../constants/bookingConstants';
import { DEFAULT_BOOKING_TITLE, PEOPLE } from '../constants/bookingConstants';
import { useDisabledOptions } from './useDisabledOptions';
export function useBookingState(addBooking) {
@@ -98,11 +98,24 @@ export function useBookingState(addBooking) {
}
}, [selectedEndIndex]);
const handleParticipantChange = useCallback((participant) => {
if (participant !== null) {
setParticipants(prev => [...prev, participant.trim()]);
const handleParticipantChange = useCallback((participantId) => {
console.log('handleParticipantChange called with:', participantId);
if (participantId !== null && participantId !== undefined) {
// Find the person by ID and add their name
const person = PEOPLE.find(p => p.id === participantId);
console.log('Found person:', person);
if (person && !participants.includes(person.name)) {
console.log('Adding participant:', person.name);
setParticipants(prev => [...prev, person.name]);
} else {
console.log('Participant already exists or person not found');
}
setParticipant("");
}
}, [participants]);
const handleRemoveParticipant = useCallback((participantToRemove) => {
setParticipants(prev => prev.filter(p => p !== participantToRemove));
}, []);
// Memoize the return object to prevent unnecessary re-renders
@@ -134,6 +147,7 @@ export function useBookingState(addBooking) {
handleSave,
handleTimeCardExit,
handleParticipantChange,
handleRemoveParticipant,
}), [
timeSlotsByRoom,
currentRoom,
@@ -155,5 +169,6 @@ export function useBookingState(addBooking) {
handleSave,
handleTimeCardExit,
handleParticipantChange,
handleRemoveParticipant,
]);
}

View File

@@ -3,7 +3,7 @@ import styles from './NewBooking.module.css';
import { TimeCardContainer } from '../components/TimeCardContainer';
import { BookingDatePicker } from '../components/BookingDatePicker';
import { BookingTitleField } from '../components/BookingTitleField';
import { ParticipantsField } from '../components/ParticipantsField';
import { ParticipantsSelector } from '../components/ParticipantsSelector';
import { RoomSelectionField } from '../components/RoomSelectionField';
import { BookingLengthField } from '../components/BookingLengthField';
import { useBookingState } from '../hooks/useBookingState';
@@ -19,7 +19,7 @@ export function NewBooking({ addBooking }) {
<div className={styles.formContainer}>
<main style={{ flex: 1 }}>
<BookingTitleField />
<ParticipantsField />
<ParticipantsSelector />
<div className={styles.bookingTimesContainer}>
<BookingDatePicker />