chore: clean up product display in hotel and cleaner interfacing

This commit is contained in:
2025-12-05 11:45:16 +01:00
parent 93fb465cbb
commit 9041af2979
7 changed files with 60 additions and 19 deletions

View File

@@ -290,8 +290,14 @@ async def get_products(
query = supabase.table(table).select('*') query = supabase.table(table).select('*')
# filter by exact date_index if provided # filter by exact date_index if provided
# dateIndex from frontend is days from today, convert to days since epoch
if dateIndex is not None: if dateIndex is not None:
query = query.eq('date_index', dateIndex) from datetime import datetime
epoch = datetime(1970, 1, 1)
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_index = int((today - epoch).total_seconds() / 86400)
actual_date_index = today_index + dateIndex
query = query.eq('date_index', actual_date_index)
response = query.execute() response = query.execute()
results = response.data results = response.data

View File

@@ -96,7 +96,10 @@ export default function CartPage() {
<span className="text-3xl font-bold">${total.toFixed(2)}</span> <span className="text-3xl font-bold">${total.toFixed(2)}</span>
</div> </div>
<button <button
onClick={() => dispatchInteraction('checkout_start', undefined, { total, itemCount })} onClick={() => {
dispatchInteraction('checkout_start', undefined, { total, itemCount });
window.location.href = '/checkout';
}}
className="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors" className="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
> >
Proceed to Checkout Proceed to Checkout

View File

@@ -21,7 +21,7 @@ const AmenityIcon = ({ name }: { name: string }) => {
breakfast: 'Breakfast', breakfast: 'Breakfast',
spa: 'Spa', spa: 'Spa',
}; };
return <span className="feature-tag">{iconMap[name.toLowerCase()] || name}</span>; return <span className="feature-tag">{iconMap[name.toLowerCase()] || name.replaceAll("_", " ")}</span>;
}; };
export default function HotelCard({ hotel }: { hotel: Hotel }) { export default function HotelCard({ hotel }: { hotel: Hotel }) {
@@ -72,7 +72,6 @@ export default function HotelCard({ hotel }: { hotel: Hotel }) {
<div className="hotel-info"> <div className="hotel-info">
<h3 ref={titleRef} className="hotel-name">{hotel.name}</h3> <h3 ref={titleRef} className="hotel-name">{hotel.name}</h3>
<div className="hotel-location text-sm mb-2">{hotel.roomType}</div>
<div className="text-sm text-[var(--text-secondary)] mb-2"> <div className="text-sm text-[var(--text-secondary)] mb-2">
{hotel.checkIn} - {hotel.checkOut} {hotel.checkIn} - {hotel.checkOut}
</div> </div>

View File

@@ -67,7 +67,6 @@ export default function HotelDetails({ product, onAddToCart, addedToCart }: Hote
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
<div className="border-b pb-6 mb-6"> <div className="border-b pb-6 mb-6">
<h1 className="text-4xl font-bold text-gray-900 mb-2">{product.name}</h1> <h1 className="text-4xl font-bold text-gray-900 mb-2">{product.name}</h1>
<p className="text-xl text-gray-500">{product.roomType}</p>
</div> </div>
<div className="grid grid-cols-2 gap-8 mb-8"> <div className="grid grid-cols-2 gap-8 mb-8">
@@ -86,7 +85,7 @@ export default function HotelDetails({ product, onAddToCart, addedToCart }: Hote
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{product.amenities.map(a => ( {product.amenities.map(a => (
<span key={a} className="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-md text-sm font-medium"> <span key={a} className="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-md text-sm font-medium">
{a} {a.replaceAll('_', ' ')}
</span> </span>
))} ))}
</div> </div>
@@ -98,11 +97,6 @@ export default function HotelDetails({ product, onAddToCart, addedToCart }: Hote
<div className="mb-3"> <div className="mb-3">
<PriceDisplay productId={product.id} className="!text-2xl" /> <PriceDisplay productId={product.id} className="!text-2xl" />
</div> </div>
<p className="text-sm text-gray-500 mb-1">Total for {product.nights} night{product.nights > 1 ? 's' : ''}</p>
<div className="flex items-baseline gap-2">
<PriceTotalDisplay productId={product.id} nights={product.nights} />
<span className="text-gray-500">/ {product.nights} nights</span>
</div>
</div> </div>
<button <button

View File

@@ -1,7 +1,29 @@
import { InputHTMLAttributes } from 'react'; import { InputHTMLAttributes, useMemo } from 'react';
interface DateInpProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {} interface DateInpProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {}
export default function DateInput({ className = '', ...props }: DateInpProps) { export default function DateInput({ className = '', ...props }: DateInpProps) {
return <input type="date" className={`input-field ${className}`.trim()} {...props} />; const { minDate, maxDate } = useMemo(() => {
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const tenDaysOut = new Date(tomorrow);
tenDaysOut.setDate(tomorrow.getDate() + 9); // tomorrow + 9 = 10 days total
return {
minDate: tomorrow.toISOString().split('T')[0],
maxDate: tenDaysOut.toISOString().split('T')[0]
};
}, []);
return (
<input
type="date"
className={`input-field ${className}`.trim()}
min={minDate}
max={maxDate}
{...props}
/>
);
} }

View File

@@ -20,7 +20,7 @@ const NavLink = ({ href, children }: { href: string; children: React.ReactNode }
href={href} href={href}
className={`px-4 py-2 rounded-md transition-colors ${ className={`px-4 py-2 rounded-md transition-colors ${
isActive isActive
? 'bg-[var(--accent-primary)] text-white font-semibold' ? 'bg-[var(--accent-primary)] font-semibold'
: 'hover:bg-[var(--accent-primary-light)] text-[var(--text-primary)]' : 'hover:bg-[var(--accent-primary-light)] text-[var(--text-primary)]'
}`} }`}
> >
@@ -37,9 +37,7 @@ export default function Navigation() {
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<NavLink href="/">Home</NavLink> <NavLink href="/">Home</NavLink>
<NavLink href="/products">Products</NavLink> <NavLink href="/products">Products</NavLink>
<NavLink href="/search">Search</NavLink>
<NavLink href="/cart">Cart</NavLink> <NavLink href="/cart">Cart</NavLink>
<NavLink href="/checkout">Checkout</NavLink>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -29,16 +29,35 @@ const EPOCH = new Date(0);
export const transformProduct = (p: HotelProduct): Hotel => { export const transformProduct = (p: HotelProduct): Hotel => {
const { id, room_type, date_index, metadata } = p; const { id, room_type, date_index, metadata } = p;
const checkIn = new Date(EPOCH.getTime() + date_index * 86400000);
// DB stores date_index as days since epoch
// but if value is small (<1000), treat as days from today for backward compat
let checkIn: Date;
if (date_index < 1000) {
// legacy: treat as offset from today
const today = new Date();
today.setHours(0, 0, 0, 0);
checkIn = new Date(today.getTime() + date_index * 86400000);
} else {
// proper: days since epoch
checkIn = new Date(EPOCH.getTime() + date_index * 86400000);
}
const nights = 1; const nights = 1;
const checkOut = new Date(checkIn.getTime() + nights * 86400000); const checkOut = new Date(checkIn.getTime() + nights * 86400000);
const formatOpts: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: checkIn.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined
};
return { return {
id, id,
name: metadata?.name || room_type, name: metadata?.name || room_type,
roomType: room_type, roomType: room_type,
checkIn: checkIn.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), checkIn: checkIn.toLocaleDateString('en-US', formatOpts),
checkOut: checkOut.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), checkOut: checkOut.toLocaleDateString('en-US', formatOpts),
dateIndex: date_index, dateIndex: date_index,
amenities: metadata?.amenities || [], amenities: metadata?.amenities || [],
pricePerNight: metadata?.base_price || 100, pricePerNight: metadata?.base_price || 100,