feat: room status table view with all rooms

This commit is contained in:
2026-07-03 00:34:43 -05:00
parent 4e514c2fbb
commit 9286f3e323
+76 -160
View File
@@ -1814,139 +1814,73 @@ function RoomStatusSection({ onToast }: { onToast: (msg: string, type: 'success'
) : rooms.length === 0 ? ( ) : rooms.length === 0 ? (
<Empty icon="🏨" title="No rooms" desc="Add rooms in the Rooms section" /> <Empty icon="🏨" title="No rooms" desc="Add rooms in the Rooms section" />
) : ( ) : (
<div style={{ display: 'grid', gap: '12px' }}> <>
{rooms.map(room => { {/* Compact Room Availability Table */}
const statusColors = getStatusColor(room.clean_status); <div style={{ background: '#fff', borderRadius: 12, border: `1px solid ${C.border}`, marginBottom: '16px', overflow: 'hidden' }}>
const priorityStyle = getPriorityStyle(room.priority); <table style={{ width: '100%', borderCollapse: 'collapse' }}>
const isEditing = editingRoom === room.id; <thead>
<tr style={{ background: C.navy, color: '#fff' }}>
return ( <th style={{ padding: '12px 16px', textAlign: 'left', fontWeight: 600 }}>Room</th>
<div key={room.id} style={{ background: '#fff', borderRadius: 12, border: `1px solid ${C.border}`, overflow: 'hidden' }}> <th style={{ padding: '12px 16px', textAlign: 'center', fontWeight: 600 }}>Status</th>
{/* Header Row */} <th style={{ padding: '12px 16px', textAlign: 'center', fontWeight: 600 }}>Occupancy</th>
<div style={{ display: 'flex', alignItems: 'center', padding: '12px 16px', background: room.is_vip ? '#fef3c7' : room.occupancy_status === 'occupied' ? '#f0fdf4' : '#fafafa', gap: '12px' }}> <th style={{ padding: '12px 16px', textAlign: 'center', fontWeight: 600 }}>Priority</th>
{/* Room Name & Status */} <th style={{ padding: '12px 16px', textAlign: 'left', fontWeight: 600 }}>Guest</th>
<div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: '10px', flexWrap: 'wrap' }}> <th style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 600 }}>Price</th>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}> </tr>
<span style={{ fontSize: '20px' }}>{getStatusIcon(room.clean_status)}</span> </thead>
<span style={{ fontWeight: 600, fontSize: '16px', color: C.navy }}>{room.name}</span> <tbody>
</div> {rooms.map((room, i) => (
{room.is_vip && ( <tr key={room.id} style={{ background: i % 2 === 0 ? '#fff' : '#f9fafb', cursor: 'pointer' }} onClick={() => { setEditingRoom(editingRoom === room.id ? null : room.id); if (editingRoom !== room.id) startEdit(room); }}>
<span style={{ padding: '2px 8px', borderRadius: 12, fontSize: '11px', fontWeight: 700, background: '#fbbf24', color: '#78350f' }}> <td style={{ padding: '10px 16px', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '8px' }}>
VIP {room.is_vip && <span></span>}
{room.name}
</td>
<td style={{ padding: '10px 16px', textAlign: 'center' }}>
<span style={{ padding: '4px 10px', borderRadius: 12, fontSize: '11px', fontWeight: 600, background: getStatusColor(room.clean_status).bg, color: getStatusColor(room.clean_status).color }}>
{getStatusIcon(room.clean_status)} {room.clean_status}
</span> </span>
)} </td>
<span style={{ <td style={{ padding: '10px 16px', textAlign: 'center' }}>
padding: '4px 10px', <span style={{ padding: '4px 10px', borderRadius: 12, fontSize: '11px', fontWeight: 600, background: room.occupancy_status === 'occupied' ? '#d1fae5' : '#e0e7ff', color: room.occupancy_status === 'occupied' ? '#047857' : '#4338ca' }}>
borderRadius: 12,
fontSize: '12px',
fontWeight: 600,
background: statusColors.bg,
color: statusColors.color,
border: `1px solid ${statusColors.border}`
}}>
{room.clean_status}
</span>
<span style={{
padding: '4px 10px',
borderRadius: 12,
fontSize: '12px',
fontWeight: 600,
background: room.occupancy_status === 'occupied' ? '#d1fae5' : '#e0e7ff',
color: room.occupancy_status === 'occupied' ? '#047857' : '#4338ca'
}}>
{room.occupancy_status === 'occupied' ? '🛏️ Occupied' : '🚪 Available'} {room.occupancy_status === 'occupied' ? '🛏️ Occupied' : '🚪 Available'}
</span> </span>
<span style={{ </td>
padding: '4px 10px', <td style={{ padding: '10px 16px', textAlign: 'center' }}>
borderRadius: 12, <span style={{ padding: '4px 8px', borderRadius: 12, fontSize: '11px', fontWeight: 600, background: getPriorityStyle(room.priority).bg, color: getPriorityStyle(room.priority).color }}>
fontSize: '12px', {getPriorityStyle(room.priority).icon} {room.priority}
fontWeight: 600,
background: priorityStyle.bg,
color: priorityStyle.color
}}>
{priorityStyle.icon} {room.priority}
</span>
{room.issues_count > 0 && (
<span style={{ padding: '2px 8px', borderRadius: 12, fontSize: '11px', fontWeight: 600, background: '#fee2e2', color: '#b91c1c' }}>
{room.issues_count} issue{room.issues_count > 1 ? 's' : ''}
</span> </span>
</td>
<td style={{ padding: '10px 16px', fontSize: '13px' }}>
{room.current_guest ? (
<span>{room.current_guest} {getPaymentBadge(room.payment_status)}</span>
) : (
<span style={{ color: '#9ca3af' }}></span>
)} )}
</div> </td>
<td style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 600, color: C.gold }}>
{/* Price */} ${parseFloat(room.price).toFixed(2)}
<div style={{ fontWeight: 600, color: C.gold, fontSize: '15px' }}> </td>
${parseFloat(room.price).toFixed(2)}/night </tr>
</div>
{/* Edit Button */}
<Btn size="sm" variant="secondary" onClick={() => isEditing ? setEditingRoom(null) : startEdit(room)}>
{isEditing ? 'Cancel' : 'Edit'}
</Btn>
</div>
{/* Details Row */}
<div style={{ padding: '12px 16px', borderTop: `1px solid ${C.border}` }}>
{/* Quick Info Grid */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '8px', marginBottom: '12px' }}>
{/* Current Guest */}
{room.current_guest && (
<div style={{ fontSize: '13px' }}>
<span style={{ color: '#666' }}>Guest: </span>
<span style={{ fontWeight: 600, color: C.navy }}>{room.current_guest}</span>
{getPaymentBadge(room.payment_status)}
</div>
)}
{/* Checkout */}
{room.checkout_date && (
<div style={{ fontSize: '13px' }}>
<span style={{ color: '#666' }}>Checkout: </span>
<span style={{ fontWeight: 500 }}>{formatDate(room.checkout_date)}</span>
{room.checkout_time && <span> at {formatTime(room.checkout_time)}</span>}
</div>
)}
{/* Last Cleaned */}
{room.last_cleaned && (
<div style={{ fontSize: '13px' }}>
<span style={{ color: '#666' }}>Last cleaned: </span>
<span style={{ fontWeight: 500 }}>{formatDateTime(room.last_cleaned)}</span>
</div>
)}
{/* Assigned Staff */}
{room.assigned_staff && (
<div style={{ fontSize: '13px' }}>
<span style={{ color: '#666' }}>Staff: </span>
<span style={{ fontWeight: 500 }}>{room.assigned_staff.first_name || ''} {room.assigned_staff.last_name || ''}</span>
</div>
)}
</div>
{/* Upcoming Reservations */}
{room.upcoming_reservations && room.upcoming_reservations.length > 0 && (
<div style={{ marginBottom: '8px' }}>
<div style={{ fontSize: '12px', color: '#888', marginBottom: '4px' }}>Upcoming:</div>
{room.upcoming_reservations.map((res, i) => (
<div key={res.id || i} style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px', marginBottom: '2px' }}>
<span>📅 {formatDate(res.check_in)} {formatDate(res.check_out)}</span>
<span style={{ fontWeight: 500 }}>{res.guest_name || res.first_name || 'Guest'}</span>
{getPaymentBadge(res.payment_status)}
</div>
))} ))}
</tbody>
</table>
</div> </div>
)}
{/* Notes */} {/* Detailed Room Cards (click to expand) */}
{room.notes && !isEditing && ( {editingRoom !== null && (
<div style={{ fontSize: '13px', color: '#666', fontStyle: 'italic' }}> <div style={{ background: '#fff', borderRadius: 12, border: `1px solid ${C.border}`, overflow: 'hidden' }}>
📝 {room.notes} {rooms.filter(r => r.id === editingRoom).map(room => {
const isEditing = true;
const statusColors = getStatusColor(room.clean_status);
const priorityStyle = getPriorityStyle(room.priority);
return (
<div key={room.id} style={{ padding: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
<h3 style={{ margin: 0, fontSize: '18px', fontWeight: 700, color: C.navy }}>
{room.is_vip && '⭐ '}{room.name}
</h3>
<Btn size="sm" variant="secondary" onClick={() => setEditingRoom(null)}>Close</Btn>
</div> </div>
)}
{/* Edit Form */}
{isEditing && (
<div style={{ marginTop: '12px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '12px' }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '12px' }}>
<S <S
label="Clean Status" label="Clean Status"
@@ -1978,55 +1912,37 @@ function RoomStatusSection({ onToast }: { onToast: (msg: string, type: 'success'
]} ]}
/> />
</div> </div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '12px' }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '12px', marginTop: '12px' }}>
<I <I label="Issues Count" type="number" value={editData.issues_count.toString()} onChange={(v: string) => setEditData(prev => ({ ...prev, issues_count: parseInt(v) || 0 }))} placeholder="0" />
label="Issues Count" <I label="Checkout Date" type="date" value={editData.checkout_date} onChange={(v: string) => setEditData(prev => ({ ...prev, checkout_date: v }))} />
type="number" <I label="Checkout Time" type="time" value={editData.checkout_time} onChange={(v: string) => setEditData(prev => ({ ...prev, checkout_time: v }))} />
value={editData.issues_count.toString()}
onChange={(v: string) => setEditData(prev => ({ ...prev, issues_count: parseInt(v) || 0 }))}
placeholder="0"
/>
<I
label="Checkout Date"
type="date"
value={editData.checkout_date}
onChange={(v: string) => setEditData(prev => ({ ...prev, checkout_date: v }))}
/>
<I
label="Checkout Time"
type="time"
value={editData.checkout_time}
onChange={(v: string) => setEditData(prev => ({ ...prev, checkout_time: v }))}
/>
</div> </div>
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}> <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', marginTop: '12px' }}>
<input <input type="checkbox" checked={editData.is_vip} onChange={(e) => setEditData(prev => ({ ...prev, is_vip: e.target.checked }))} style={{ width: 18, height: 18 }} />
type="checkbox"
checked={editData.is_vip}
onChange={(e) => setEditData(prev => ({ ...prev, is_vip: e.target.checked }))}
style={{ width: 18, height: 18 }}
/>
<span style={{ fontWeight: 500 }}> VIP Guest</span> <span style={{ fontWeight: 500 }}> VIP Guest</span>
</label> </label>
<TA <TA label="Notes" value={editData.notes} onChange={(v: string) => setEditData(prev => ({ ...prev, notes: v }))} rows={2} placeholder="e.g., Need extra towels, AC broken..." style={{ marginTop: '12px' }} />
label="Notes" <div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
value={editData.notes}
onChange={(v: string) => setEditData(prev => ({ ...prev, notes: v }))}
rows={2}
placeholder="e.g., Need extra towels, AC broken..."
/>
<div style={{ display: 'flex', gap: '8px' }}>
<Btn onClick={updateRoomStatus} size="sm">Save</Btn> <Btn onClick={updateRoomStatus} size="sm">Save</Btn>
<Btn variant="secondary" onClick={() => setEditingRoom(null)} size="sm">Cancel</Btn> <Btn variant="secondary" onClick={() => setEditingRoom(null)} size="sm">Cancel</Btn>
</div> </div>
{room.upcoming_reservations && room.upcoming_reservations.length > 0 && (
<div style={{ marginTop: '16px', padding: '12px', background: '#f9fafb', borderRadius: 8 }}>
<div style={{ fontWeight: 600, marginBottom: '8px' }}>📅 Upcoming Reservations</div>
{room.upcoming_reservations.map((res, i) => (
<div key={res.id || i} style={{ fontSize: '13px', marginBottom: '4px' }}>
{formatDate(res.check_in)} {formatDate(res.check_out)}: {res.guest_name || res.first_name || 'Guest'}
</div>
))}
</div> </div>
)} )}
</div> </div>
</div>
); );
})} })}
</div> </div>
)} )}
</>
)}
</div> </div>
); );
} }