// CMS pages: list views for packages, vehicles, blogs, pages, inquiries, users, menu, settings // ---- Reusable table ---- const CmsTable = ({ columns, rows, onRowClick, empty = 'No items.' }) => (
{columns.map(c => ( ))} {rows.length === 0 && } {rows.map((r, ri) => ( onRowClick && onRowClick(r)} style={{ borderBottom: '1px solid #f3ecd9', cursor: onRowClick ? 'pointer' : 'default', transition: 'background .12s' }} onMouseEnter={e => e.currentTarget.style.background = '#FBFAF4'} onMouseLeave={e => e.currentTarget.style.background = 'white'}> {columns.map(c => )} ))}
{c.label}
{empty}
{c.render ? c.render(r) : r[c.key]}
); // ---- Packages list ---- const CmsPackages = ({ state, setState, setCmsRoute }) => { const [filter, setFilter] = useState('all'); const [q, setQ] = useState(''); const list = state.packages .filter(p => filter === 'all' || p.status === filter) .filter(p => !q || p.title.toLowerCase().includes(q.toLowerCase()) || p.destination.toLowerCase().includes(q.toLowerCase())); const onDelete = id => { if (!confirm('Delete this package? This cannot be undone.')) return; setState(s => ({ ...s, packages: s.packages.filter(p => p.id !== id) })); }; const togglePublish = (id) => { setState(s => ({ ...s, packages: s.packages.map(p => p.id === id ? { ...p, status: p.status === 'published' ? 'draft' : 'published' } : p) })); }; return ( <> p.status === 'published').length} live on site`} actions={<> Export CSV setCmsRoute({ route: 'packages-edit', id: 'new' })}>Add package } />
{[['all', 'All'], ['published', 'Published'], ['draft', 'Draft']].map(([k, l]) => ( ))}
setQ(e.target.value)} placeholder="Search title, destination..." style={{ width: '100%', padding: '7px 12px 7px 30px', border: '1px solid #eae4d2', borderRadius: 8, fontSize: 13, background: '#F7F5F0', outline: 'none' }} />
(
{p.title}
{p.destination} · {p.duration}
) }, { key: 'category', label: 'Type', render: p => {p.category === 'inbound' ? 'Inbound' : 'Outbound'} }, { key: 'priceFrom', label: 'Price from', render: p => {fmtMYR(p.priceFrom)} }, { key: 'featured', label: 'Featured', render: p => p.featured ? : }, { key: 'status', label: 'Status', render: p => }, { key: 'created', label: 'Created', render: p => {fmtDate(p.created)} }, { key: 'actions', label: '', render: p => (
e.stopPropagation()}>
) }, ]} rows={list} onRowClick={p => setCmsRoute({ route: 'packages-edit', id: p.id })} />
); }; // ---- Vehicles list ---- const CmsVehicles = ({ state, setState, setCmsRoute }) => ( <> v.available).length} available`} actions={ setCmsRoute({ route: 'vehicles-edit', id: 'new' })}>Add vehicle} />
(
{v.name}
{v.type}
) }, { key: 'capacity', label: 'Capacity', render: v => {v.capacity} pax · {v.luggage} bags }, { key: 'rate', label: 'Daily rate', render: v => {fmtMYR(v.rate)} }, { key: 'available', label: 'Availability', render: v => {v.available ? 'Available' : 'Unavailable'} }, { key: 'status', label: 'Status', render: v => }, ]} rows={state.vehicles} onRowClick={v => setCmsRoute({ route: 'vehicles-edit', id: v.id })} />
); // ---- Blog list ---- const CmsBlogs = ({ state, setState, setCmsRoute }) => ( <> b.status === 'draft').length} in draft`} actions={ setCmsRoute({ route: 'blogs-edit', id: 'new' })}>New post} />
(
{b.title}
{b.excerpt.slice(0, 60)}...
) }, { key: 'category', label: 'Category' }, { key: 'author', label: 'Author' }, { key: 'date', label: 'Date', render: b => {fmtDate(b.date)} }, { key: 'status', label: 'Status', render: b => }, ]} rows={state.blogs} onRowClick={b => setCmsRoute({ route: 'blogs-edit', id: b.id })} />
); // ---- Pages list ---- const CmsPages = ({ state, setState, setCmsRoute }) => ( <> setCmsRoute({ route: 'pages-edit', id: 'new' })}>New page} />
(
{p.title}
/{p.slug}
) }, { key: 'inMenu', label: 'In menu', render: p => p.inMenu ? : }, { key: 'status', label: 'Status', render: p => }, { key: 'updated', label: 'Updated', render: p => {fmtDate(p.updated)} }, ]} rows={state.pages} onRowClick={p => setCmsRoute({ route: 'pages-edit', id: p.id })} />
); // ---- Inquiries ---- const CmsInquiries = ({ state, setState, cmsRoute, setCmsRoute }) => { const [filter, setFilter] = useState('all'); const [selected, setSelected] = useState(cmsRoute.openId || null); const list = state.inquiries.filter(i => filter === 'all' || i.status === filter); const open = list.find(i => i.id === selected) || state.inquiries.find(i => i.id === selected); const setStatus = (id, status) => setState(s => ({ ...s, inquiries: s.inquiries.map(i => i.id === id ? { ...i, status } : i) })); return ( <> i.status === 'new').length} new · ${state.inquiries.filter(i => i.status === 'read').length} read · ${state.inquiries.filter(i => i.status === 'responded').length} responded`} actions={Export} />
{[['all', 'All'], ['new', 'New'], ['read', 'Read'], ['responded', 'Responded']].map(([k, l]) => { const count = k === 'all' ? state.inquiries.length : state.inquiries.filter(i => i.status === k).length; return ( ); })}
(
{i.name.split(' ').map(x => x[0]).join('').slice(0,2)}
{i.name} {i.status === 'new' && }
{i.email}
) }, { key: 'service', label: 'Service', render: i => {i.service} }, { key: 'message', label: 'Message', render: i =>
{i.message}
}, { key: 'date', label: 'Date', render: i => {relTime(i.date)} }, { key: 'status', label: 'Status', render: i => }, ]} rows={list} onRowClick={i => { setSelected(i.id); if (i.status === 'new') setStatus(i.id, 'read'); }} />
{open && ( )}
); }; // ---- Users ---- const CmsUsers = ({ state, setState }) => { const [modal, setModal] = useState(null); const [form, setForm] = useState({ name: '', email: '', role: 'Editor' }); return ( <> { setForm({ name: '', email: '', role: 'Editor' }); setModal('new'); }}>Invite user} />
(
{u.avatar}
{u.name}
) }, { key: 'email', label: 'Email' }, { key: 'role', label: 'Role', render: u => {u.role} }, { key: 'lastLogin', label: 'Last login', render: u => {u.lastLogin} }, ]} rows={state.users} />
setModal(null)} title="Invite a new user" subtitle="They'll receive an email to set their password.">
setForm({ ...form, name: v })} /> setForm({ ...form, email: v })} /> updateItem(it.id, { label: e.target.value })} style={{ border: '1px solid #eae4d2', borderRadius: 8, padding: '7px 10px', fontSize: 13, outline: 'none', background: '#F7F5F0' }} /> updateItem(it.id, { link: e.target.value })} style={{ border: '1px solid #eae4d2', borderRadius: 8, padding: '7px 10px', fontSize: 13, outline: 'none', fontFamily: 'Geist Mono', background: '#F7F5F0' }} /> updateItem(it.id, { visible: v })} />
))} ); }; // ---- Settings ---- const CmsSettings = ({ state, setState }) => { const [tab, setTab] = useState('agency'); const [form, setForm] = useState(state.settings); const { show, node } = useToast(); const save = () => { setState(s => ({ ...s, settings: form })); show('Settings saved.'); }; return ( <> Save changes} />
{[['agency', 'Agency info', 'building'], ['homepage', 'Homepage', 'home'], ['contact', 'Contact', 'phone'], ['social', 'Social', 'share-2'], ['advanced', 'Advanced', 'settings']].map(([k, l, ic]) => ( ))}
{tab === 'agency' &&

Agency information

setForm({ ...form, agencyName: v })} /> setForm({ ...form, tagline: v })} /> setForm({ ...form, license: v })} />
Logo
P
Upload new
} {tab === 'homepage' &&

Homepage

setForm({ ...form, heroImage: v })} />