NewBook-connected daily housekeeping planner. Replaces the hotelhubmodule-housekeeping-dailylist WordPress plugin. LXC 120 · 10.10.10.120:3080 · slug: room-planner. - 3-day booking window (yesterday/today/tomorrow) fetched live from NewBook - Task completion ticks back to NewBook; room status patches NewBook directly - 23px border sliver CSS system for adjacent-day booking status - 3-state filter cycling (off→inclusive→exclusive) for categories and flow types - Stat filters for outstanding tasks and clean/dirty status - Rolling 48h activity log with checkout/checkin/status/tasks events - newbook_pings event bus for future NewBook poller integration - Room modal with permission-gated guest/rate/notes, task checkboxes, status buttons - Placeholder sections for future linen-count and routine-tasks modules - Settings page: task type colours, twin/extra-bed detection, category exclusions - Mobile-first layout (sidebar desktop, compact top bar mobile) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import type { ActivityEntry } from '../types'
|
|
|
|
interface Props {
|
|
entries: ActivityEntry[]
|
|
}
|
|
|
|
const EVENT_LABELS: Record<ActivityEntry['event_type'], string> = {
|
|
checkout: 'Checked out',
|
|
checkin: 'Checked in',
|
|
status_clean: 'Marked clean',
|
|
status_dirty: 'Marked dirty',
|
|
tasks_complete: 'All tasks done',
|
|
}
|
|
|
|
const EVENT_CLASS: Record<ActivityEntry['event_type'], string> = {
|
|
checkout: 'event-checkout',
|
|
checkin: 'event-checkin',
|
|
status_clean: 'event-clean',
|
|
status_dirty: 'event-dirty',
|
|
tasks_complete: 'event-tasks',
|
|
}
|
|
|
|
function relativeTime(iso: string) {
|
|
const diff = Date.now() - new Date(iso).getTime()
|
|
const mins = Math.floor(diff / 60000)
|
|
if (mins < 1) return 'Just now'
|
|
if (mins < 60) return `${mins}m ago`
|
|
const hrs = Math.floor(mins / 60)
|
|
if (hrs < 24) return `${hrs}h ago`
|
|
return new Date(iso).toLocaleDateString()
|
|
}
|
|
|
|
export default function ActivityPanel({ entries }: Props) {
|
|
return (
|
|
<aside className="activity-panel">
|
|
<div className="activity-panel-header">Recent Changes</div>
|
|
<div className="activity-list">
|
|
{entries.length === 0 ? (
|
|
<div style={{ padding: '16px 12px', color: 'var(--text-muted)', fontSize: 12 }}>
|
|
No activity yet today
|
|
</div>
|
|
) : (
|
|
entries.map(entry => (
|
|
<div key={entry.id} className="activity-item">
|
|
<div className="activity-room">{entry.room_id}</div>
|
|
<div className={`activity-event ${EVENT_CLASS[entry.event_type]}`}>
|
|
{EVENT_LABELS[entry.event_type]}
|
|
</div>
|
|
{entry.user_name && (
|
|
<div className="activity-time">{entry.user_name}</div>
|
|
)}
|
|
<div className="activity-time">{relativeTime(entry.occurred_at)}</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|