// CMS admin shell: sidebar + topbar + dashboard const CmsShell = ({ state, cmsRoute, setCmsRoute, children }) => { const nav = [ { group: 'Overview', items: [ { id: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' }, ]}, { group: 'Content', items: [ { id: 'packages', label: 'Tour packages', icon: 'compass', count: state.packages.length }, { id: 'vehicles', label: 'Vehicles', icon: 'car', count: state.vehicles.length }, { id: 'blogs', label: 'Blog', icon: 'book-open', count: state.blogs.length }, { id: 'pages', label: 'Pages', icon: 'file-text', count: state.pages.length }, ]}, { group: 'Operations', items: [ { id: 'inquiries', label: 'Inquiries', icon: 'mail', count: state.inquiries.filter(i => i.status === 'new').length, badge: 'new' }, { id: 'menu', label: 'Navigation', icon: 'menu' }, ]}, { group: 'Admin', items: [ { id: 'users', label: 'Users', icon: 'users' }, { id: 'settings', label: 'Settings', icon: 'settings' }, ]}, ]; return (
{/* Sidebar */} {/* Main */}
PL
Peter Lau
Admin
{children}
); }; // -------- DASHBOARD -------- const CmsDashboard = ({ state, setCmsRoute }) => { const newInquiries = state.inquiries.filter(i => i.status === 'new').length; const published = state.packages.filter(p => p.status === 'published').length; const drafts = state.packages.filter(p => p.status === 'draft').length; const blogDrafts = state.blogs.filter(b => b.status === 'draft').length; const stats = [ { label: 'Tour packages', value: state.packages.length, delta: '+2 this month', icon: 'compass' }, { label: 'Published', value: published, delta: drafts + ' in draft', icon: 'check-circle' }, { label: 'New inquiries', value: newInquiries, delta: 'needs attention', icon: 'mail', highlight: true }, { label: 'Blog posts', value: state.blogs.length, delta: blogDrafts + ' in draft', icon: 'book-open' }, ]; return (
{new Date().toLocaleDateString('en-MY', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}

Selamat pagi, Peter.

You have {newInquiries} new inquiries waiting for a reply.
setCmsRoute({ route: 'packages-edit', id: 'new' })}>New package setCmsRoute({ route: 'blogs-edit', id: 'new' })}>New blog post setCmsRoute({ route: 'pages-edit', id: 'new' })}>New page
{/* Stats */}
{stats.map(s => (
{s.highlight &&
}
{s.label}
{s.value}
{s.delta}
))}
{/* Recent inquiries */}

Recent inquiries

{state.inquiries.slice(0, 6).map(i => (
setCmsRoute({ route: 'inquiries', openId: i.id })} style={{ padding: '14px 20px', borderBottom: '1px solid #f3ecd9', display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}>
{i.name.split(' ').map(x => x[0]).join('').slice(0,2)}
{i.name} {i.status === 'new' && }
{i.service} · {i.message.slice(0, 70)}...
{relTime(i.date)}
))}
{/* Activity */}

Quick links

{[ { route: 'packages', icon: 'compass', label: 'Manage tour packages' }, { route: 'blogs', icon: 'book-open', label: 'Blog posts' }, { route: 'menu', icon: 'menu', label: 'Edit site navigation' }, { route: 'settings', icon: 'settings', label: 'Site settings' }, ].map(x => ( ))}
This month
147
inquiries received · +22% vs last month
{[45, 52, 38, 60, 48, 72, 66, 80, 58, 90, 72, 85].map((h, i) => (
7 ? '#E85D2F' : 'rgba(245,238,222,0.35)', borderRadius: 2 }} /> ))}
); }; // ---- Shared CMS chrome bits ---- const CmsPageHeader = ({ title, subtitle, breadcrumb, actions }) => (
{breadcrumb &&
{breadcrumb}
}

{title}

{subtitle &&
{subtitle}
}
{actions}
); const CmsStatusBadge = ({ status }) => { const map = { published: { tone: 'green', label: 'Published' }, draft: { tone: 'neutral', label: 'Draft' }, new: { tone: 'orange', label: 'New' }, read: { tone: 'blue', label: 'Read' }, responded: { tone: 'green', label: 'Responded' }, }; const m = map[status] || { tone: 'neutral', label: status }; return {m.label}; }; Object.assign(window, { CmsShell, CmsDashboard, CmsPageHeader, CmsStatusBadge });