make calendar view mobile friendly
This commit is contained in:
parent
efaa22a8f1
commit
fcaf0c4c8a
@ -11,6 +11,18 @@ const ResourceCalendar: React.FC = () => {
|
|||||||
const [currentWeek, setCurrentWeek] = useState(new Date());
|
const [currentWeek, setCurrentWeek] = useState(new Date());
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Mobile detection
|
||||||
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@ -20,17 +32,13 @@ const ResourceCalendar: React.FC = () => {
|
|||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
console.log('Loading calendar for resource ID:', id);
|
|
||||||
const [resourceData, reservationsData] = await Promise.all([
|
const [resourceData, reservationsData] = await Promise.all([
|
||||||
api.getResource(id),
|
api.getResource(id),
|
||||||
api.getReservations(id)
|
api.getReservations(id)
|
||||||
]);
|
]);
|
||||||
console.log('Resource data:', resourceData);
|
|
||||||
console.log('Reservations data:', reservationsData);
|
|
||||||
setResource(resourceData);
|
setResource(resourceData);
|
||||||
setReservations(reservationsData);
|
setReservations(reservationsData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error loading calendar:', err);
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load data');
|
setError(err instanceof Error ? err.message : 'Failed to load data');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -43,8 +51,6 @@ const ResourceCalendar: React.FC = () => {
|
|||||||
const weekStart = startOfWeek(currentWeek, { weekStartsOn: 1 });
|
const weekStart = startOfWeek(currentWeek, { weekStartsOn: 1 });
|
||||||
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Generate time slots from 8 AM to 8 PM
|
// Generate time slots from 8 AM to 8 PM
|
||||||
const timeSlots = Array.from({ length: 13 }, (_, i) => {
|
const timeSlots = Array.from({ length: 13 }, (_, i) => {
|
||||||
const hour = i + 8; // Start at 8 AM
|
const hour = i + 8; // Start at 8 AM
|
||||||
@ -69,7 +75,8 @@ const ResourceCalendar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const navigateWeek = (direction: 'prev' | 'next') => {
|
const navigateWeek = (direction: 'prev' | 'next') => {
|
||||||
setCurrentWeek(prev => addDays(prev, direction === 'next' ? 7 : -7));
|
const days = direction === 'next' ? 7 : -7;
|
||||||
|
setCurrentWeek(addDays(currentWeek, days));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -100,105 +107,132 @@ const ResourceCalendar: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ padding: '0.5rem', maxWidth: '100vw', overflowX: 'auto' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
|
{/* Header - Mobile Optimized */}
|
||||||
<div>
|
<div style={{ marginBottom: '1.5rem' }}>
|
||||||
<h2>{resource.name} - Calendar</h2>
|
<h2 style={{ margin: '0 0 0.5rem 0', fontSize: '1.5rem' }}>{resource.name}</h2>
|
||||||
{resource.description && (
|
{resource.description && (
|
||||||
<p style={{ color: '#666', margin: '0.5rem 0' }}>{resource.description}</p>
|
<p style={{ color: '#666', margin: '0 0 1rem 0', fontSize: '0.875rem' }}>{resource.description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<Link
|
<Link
|
||||||
to={`/reservations/new?resourceId=${resource.id}`}
|
to={`/reservations/new?resourceId=${resource.resourceId}`}
|
||||||
style={{
|
style={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
padding: '0.75rem 1.5rem',
|
width: '100%',
|
||||||
|
padding: '0.75rem',
|
||||||
backgroundColor: '#27ae60',
|
backgroundColor: '#27ae60',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
fontWeight: '500'
|
fontWeight: '500',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '0.875rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Book This Resource
|
Book This Resource
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Week Navigation */}
|
{/* Week Navigation - Mobile Optimized */}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem', padding: '1rem', backgroundColor: '#f8f9fa', borderRadius: '4px' }}>
|
<div style={{
|
||||||
<button
|
display: 'flex',
|
||||||
onClick={() => navigateWeek('prev')}
|
flexDirection: 'column',
|
||||||
style={{
|
gap: '0.75rem',
|
||||||
padding: '0.5rem 1rem',
|
marginBottom: '1.5rem',
|
||||||
backgroundColor: '#3498db',
|
padding: '1rem',
|
||||||
color: 'white',
|
backgroundColor: '#f8f9fa',
|
||||||
border: 'none',
|
borderRadius: '4px',
|
||||||
borderRadius: '4px',
|
textAlign: 'center'
|
||||||
cursor: 'pointer'
|
}}>
|
||||||
}}
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
>
|
<button
|
||||||
← Previous Week
|
onClick={() => navigateWeek('prev')}
|
||||||
</button>
|
style={{
|
||||||
<h3 style={{ margin: 0, color: '#2c3e50' }}>
|
padding: '0.5rem 1rem',
|
||||||
|
backgroundColor: '#3498db',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
flex: 1,
|
||||||
|
marginRight: '0.5rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
← Previous
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateWeek('next')}
|
||||||
|
style={{
|
||||||
|
padding: '0.5rem 1rem',
|
||||||
|
backgroundColor: '#3498db',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: '0.5rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h3 style={{ margin: '0.5rem 0 0 0', color: '#2c3e50', fontSize: '1rem' }}>
|
||||||
{format(weekStart, 'MMM d')} - {format(addDays(weekStart, 6), 'MMM d, yyyy')}
|
{format(weekStart, 'MMM d')} - {format(addDays(weekStart, 6), 'MMM d, yyyy')}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
|
||||||
onClick={() => navigateWeek('next')}
|
|
||||||
style={{
|
|
||||||
padding: '0.5rem 1rem',
|
|
||||||
backgroundColor: '#3498db',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next Week →
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Calendar Grid */}
|
{/* Calendar Grid - Mobile Responsive */}
|
||||||
<div style={{
|
<div style={{
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
backgroundColor: 'white'
|
backgroundColor: 'white',
|
||||||
|
fontSize: '0.75rem'
|
||||||
}}>
|
}}>
|
||||||
{/* Header Row */}
|
{/* Header Row - Mobile Responsive */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '100px repeat(7, 1fr)', backgroundColor: '#2c3e50', color: 'white' }}>
|
<div style={{
|
||||||
<div style={{ padding: '1rem', textAlign: 'center', fontWeight: 'bold', borderRight: '1px solid #ddd' }}>
|
display: 'grid',
|
||||||
|
gridTemplateColumns: isMobile ? '50px repeat(7, 0.8fr)' : '60px repeat(7, 1fr)',
|
||||||
|
backgroundColor: '#2c3e50',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: isMobile ? '0.6rem' : '0.7rem'
|
||||||
|
}}>
|
||||||
|
<div style={{ padding: '0.5rem', textAlign: 'center', fontWeight: 'bold', borderRight: '1px solid #ddd' }}>
|
||||||
Time
|
Time
|
||||||
</div>
|
</div>
|
||||||
{weekDays.map((day, index) => (
|
{weekDays.map((day, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
padding: '1rem',
|
padding: '0.5rem',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
borderRight: index < 6 ? '1px solid #ddd' : 'none',
|
borderRight: index < 6 ? '1px solid #ddd' : 'none',
|
||||||
backgroundColor: isToday(day) ? '#34495e' : 'transparent'
|
backgroundColor: isToday(day) ? '#34495e' : 'transparent'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{format(day, 'EEEE')}</div>
|
<div>{format(day, 'EEE')}</div>
|
||||||
<div style={{ fontSize: '0.875rem', opacity: 0.8 }}>
|
<div style={{ fontSize: '0.7rem', opacity: 0.8 }}>
|
||||||
{format(day, 'MMM d')}
|
{format(day, 'M/d')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Time Slots */}
|
{/* Time Slots - Mobile Responsive */}
|
||||||
{timeSlots.map((timeSlot, timeIndex) => (
|
{timeSlots.map((timeSlot, timeIndex) => (
|
||||||
<div key={timeIndex} style={{ display: 'grid', gridTemplateColumns: '100px repeat(7, 1fr)' }}>
|
<div key={timeIndex} style={{ display: 'grid', gridTemplateColumns: isMobile ? '50px repeat(7, 0.8fr)' : '60px repeat(7, 1fr)' }}>
|
||||||
{/* Time Column */}
|
{/* Time Column */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '0.75rem',
|
padding: '0.5rem',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
borderRight: '1px solid #ddd',
|
borderRight: '1px solid #ddd',
|
||||||
borderBottom: '1px solid #ddd',
|
borderBottom: '1px solid #ddd',
|
||||||
backgroundColor: '#f8f9fa',
|
backgroundColor: '#f8f9fa',
|
||||||
fontSize: '0.875rem'
|
fontSize: '0.7rem',
|
||||||
|
fontWeight: '500'
|
||||||
}}>
|
}}>
|
||||||
{format(timeSlot, 'h a')}
|
{format(timeSlot, 'h a')}
|
||||||
</div>
|
</div>
|
||||||
@ -215,27 +249,46 @@ const ResourceCalendar: React.FC = () => {
|
|||||||
borderRight: dayIndex < 6 ? '1px solid #ddd' : 'none',
|
borderRight: dayIndex < 6 ? '1px solid #ddd' : 'none',
|
||||||
borderBottom: '1px solid #ddd',
|
borderBottom: '1px solid #ddd',
|
||||||
backgroundColor: reservation
|
backgroundColor: reservation
|
||||||
? reservation.status === 'confirmed' ? '#d4edda'
|
? '#d4edda'
|
||||||
: reservation.status === 'cancelled' ? '#f8d7da'
|
|
||||||
: '#fff3cd'
|
|
||||||
: isToday(day) ? '#f8f9fa' : 'white',
|
: isToday(day) ? '#f8f9fa' : 'white',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.7rem',
|
||||||
minHeight: '40px',
|
minHeight: '35px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center',
|
||||||
|
position: 'relative'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{reservation && (
|
{reservation && (
|
||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: reservation.status === 'cancelled' ? '#721c24' : '#333',
|
color: '#333',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
padding: '0.25rem'
|
padding: '0.2rem',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontWeight: 'bold' }}>{reservation.title}</div>
|
<div style={{
|
||||||
<div style={{ fontSize: '0.625rem', opacity: 0.8 }}>
|
fontWeight: 'bold',
|
||||||
{format(new Date(reservation.startDate), 'h:mm')} - {format(new Date(reservation.endDate), 'h:mm')}
|
fontSize: '0.6rem',
|
||||||
|
lineHeight: '1.2',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical'
|
||||||
|
}}>
|
||||||
|
{reservation.title}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '0.55rem',
|
||||||
|
opacity: 0.8,
|
||||||
|
lineHeight: '1'
|
||||||
|
}}>
|
||||||
|
{format(new Date(reservation.startDate), 'h:mm')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user