// Rental page with filters, fleet grid, and detail drawer with floorplan
const { useState: useStateR, useMemo: useMemoR } = React;
function FilterBar({ filters, setFilters, count }) {
const groups = {
type: { label: 'Type', options: ['All', 'Solo', 'Couples', 'Family', 'Group'] },
sleeps: { label: 'Sleeps', options: ['Any', '2', '4', '5+'] },
transmission: { label: 'Transmission', options: ['Any', 'Auto', 'Manual'] },
license: { label: 'License', options: ['Any', 'B/D', 'D', 'E'] },
};
return (
{Object.entries(groups).map(([key, g]) => (
{g.label}
{g.options.map(opt => (
))}
))}
{count} VANS · 04 AVAILABLE
);
}
function RentalCard({ vehicle, onOpen }) {
return (
onOpen(vehicle)}>
{vehicle.tag}
{vehicle.available ? 'Available' : 'Booked'}
{vehicle.name}
{vehicle.sub}
Sleeps {vehicle.sleeps}
{vehicle.seats} seats
{vehicle.length}
{vehicle.transmission}
RM {vehicle.price}/ night
View
);
}
function Floorplan() {
const [active, setActive] = useStateR(FLOORPLAN_HOTSPOTS[0].id);
const cur = FLOORPLAN_HOTSPOTS.find(h => h.id === active);
return (
Interior floorplan
TAP HOTSPOTS
);
}
function RentalDrawer({ vehicle, onClose, bookingState, onBook }) {
const [tab, setTab] = useStateR('overview');
if (!vehicle) return null;
const nights = bookingState?.start && bookingState?.end
? Math.ceil((bookingState.end - bookingState.start) / 86400000) : 0;
const total = nights * vehicle.price;
return (
<>
{vehicle.tag.toUpperCase()} / {vehicle.id.toUpperCase()}
{vehicle.gallery.map((src, i) => (
))}
{vehicle.sub}
{vehicle.name}
{[
{ id: 'overview', label: 'Overview' },
{ id: 'specs', label: 'Specs' },
{ id: 'floorplan', label: 'Floorplan' },
{ id: 'price', label: 'Pricing' },
].map(t => (
))}
{tab === 'overview' && (
{vehicle.desc}
{[
{ icon:
, label: 'Sleeps', val: vehicle.sleeps },
{ icon:
, label: 'Seats', val: vehicle.seats },
{ icon:
, label: 'Length', val: vehicle.length },
{ icon:
, label: 'License', val: vehicle.license },
].map((s, i) => (
{s.icon}
{s.label.toUpperCase()}
{s.val}
))}
What's included
{['Unlimited Peninsula miles', 'Comprehensive insurance', 'RFID + first tank fuel', 'Bedding & towel set', 'Camp chairs & table', '24/7 roadside support'].map(item => (
-
{item}
))}
)}
{tab === 'specs' && (
{Object.entries(vehicle.specs).map(([k, v]) => (
))}
)}
{tab === 'floorplan' &&
}
{tab === 'price' && (
Daily rate
RM {vehicle.price}
Nights
× {nights || '—'}
Comprehensive insurance
Included
Peninsular miles
Unlimited
Subtotal
RM {total ? total.toLocaleString() : '—'}
Free cancellation up to 14 days before pickup. No deposit on weeknight pickups. Final price confirmed by our team within 1 hour.
)}
{bookingState?.start && bookingState?.end
? `${formatDate(bookingState.start)} → ${formatDate(bookingState.end)} · ${nights} NIGHTS`
: 'NO DATES SELECTED'}
{total ? `RM ${total.toLocaleString()}` : `RM ${vehicle.price} / night`}
>
);
}
function RentalPage({ bookingState, setBookingState, onToast }) {
const [filters, setFilters] = useStateR({ type: 'All', sleeps: 'Any', transmission: 'Any', license: 'Any' });
const [openVehicle, setOpenVehicle] = useStateR(null);
const filtered = useMemoR(() => {
return FLEET.filter(v => {
if (filters.type !== 'All' && v.tag !== filters.type) return false;
if (filters.sleeps !== 'Any') {
if (filters.sleeps === '5+' && v.sleeps < 5) return false;
else if (filters.sleeps !== '5+' && String(v.sleeps) !== filters.sleeps) return false;
}
if (filters.transmission !== 'Any' && v.transmission !== filters.transmission) return false;
if (filters.license !== 'Any' && v.license !== filters.license) return false;
return true;
});
}, [filters]);
const onBook = (v) => {
setOpenVehicle(null);
onToast(`${v.name} reserved — our team will confirm within 1 hour.`);
};
return (
Rental fleet · Klang HQ
Six builds. Pick the one that's already going where you're going.
Every motorhome below is hand-built and maintained in our Klang workshop. Photographed exactly as you'll find it. Pick a date, lock the keys.
{filtered.map(v => )}
{filtered.length === 0 && (
Nothing matches that combination — yet.
Loosen a filter, or call our team. We sometimes hold builds back for repeat customers.
)}
{openVehicle && (
setOpenVehicle(null)}
bookingState={bookingState}
onBook={onBook}
/>
)}
);
}
window.RentalPage = RentalPage;