Files
appcakes-builds/src/pages/OrderDetail.tsx
T
2026-05-27 17:44:06 +00:00

206 lines
6.2 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonList,
IonLoading,
IonPage,
IonText,
IonTitle,
IonToolbar,
useIonViewWillEnter,
} from '@ionic/react';
import { RouteComponentProps, useHistory } from 'react-router-dom';
import { supabase } from '../supabase';
import type { Tables } from '../database.types';
import {
setStatusBarBackground,
setStatusBarStyle,
Style,
} from '../utils/statusBar';
type Props = RouteComponentProps<{ id: string }>;
type Order = Tables<'orders'>;
type OrderItem = Tables<'order_items'>;
const statusSteps = [
'pending',
'confirmed',
'packed',
'out_for_delivery',
'delivered',
] as const;
const statusTitles: Record<(typeof statusSteps)[number], string> = {
pending: 'Pending',
confirmed: 'Confirmed',
packed: 'Packed',
out_for_delivery: 'Out for delivery',
delivered: 'Delivered',
};
const OrderDetail: React.FC<Props> = ({ match }) => {
const history = useHistory();
const [order, setOrder] = useState<Order | null>(null);
const [items, setItems] = useState<OrderItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState('');
useIonViewWillEnter(() => {
setStatusBarStyle(Style.Dark);
setStatusBarBackground('#ffffff');
});
useEffect(() => {
const loadOrder = async () => {
setIsLoading(true);
const [
{ data: orderData, error: orderError },
{ data: itemData, error: itemError },
] = await Promise.all([
supabase.from('orders').select('*').eq('id', match.params.id).single(),
supabase
.from('order_items')
.select('*')
.eq('order_id', match.params.id)
.order('created_at', { ascending: true }),
]);
if (orderError || itemError || !orderData) {
setErrorMessage('We could not load this order.');
window.setTimeout(() => setErrorMessage(''), 4000);
setOrder(null);
setItems([]);
setIsLoading(false);
return;
}
setOrder(orderData);
setItems(itemData ?? []);
setIsLoading(false);
};
loadOrder();
}, [match.params.id]);
const activeStepIndex = useMemo(() => {
if (!order) return 0;
const index = statusSteps.indexOf(
order.status as (typeof statusSteps)[number]
);
return index >= 0 ? index : 0;
}, [order]);
return (
<IonPage>
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonButton fill="clear" onClick={() => history.replace('/orders')}>
Back
</IonButton>
</IonButtons>
<IonTitle>Track Order</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<IonLoading isOpen={isLoading} message="Loading order details..." />
{errorMessage && (
<IonText color="danger">
<p>{errorMessage}</p>
</IonText>
)}
{!isLoading && !order ? (
<div className="empty-state cart-empty">
<h3>Order not found</h3>
<p className="muted-text">
We could not find that order. Try returning to your order list.
</p>
<IonButton onClick={() => history.replace('/orders')}>
Back to orders
</IonButton>
</div>
) : (
order && (
<>
<div className="tracking-summary-card">
<span className="order-label">Order number</span>
<h1>{order.order_number}</h1>
<p className="muted-text">
Placed on {new Date(order.created_at).toLocaleString()}
</p>
<div className="order-card-row">
<span>Current status</span>
<strong>
{statusTitles[
order.status as (typeof statusSteps)[number]
] ?? order.status}
</strong>
</div>
<div className="order-card-row">
<span>Total</span>
<strong>${Number(order.total).toFixed(2)}</strong>
</div>
</div>
<div className="timeline-card">
<h2>Delivery progress</h2>
<div className="tracking-timeline">
{statusSteps.map((step, index) => {
const isActive = index <= activeStepIndex;
return (
<div
key={step}
className={`timeline-step ${isActive ? 'active' : ''}`}
>
<div className="timeline-dot" />
<div>
<strong>{statusTitles[step]}</strong>
<p className="muted-text">
{index < activeStepIndex
? 'Completed'
: index === activeStepIndex
? 'Current step'
: 'Up next'}
</p>
</div>
</div>
);
})}
</div>
</div>
<div className="timeline-card">
<h2>Items in this order</h2>
<IonList lines="none" className="cart-list">
{items.map((item) => (
<IonItem key={item.id} className="cart-item">
<IonLabel>
<h2>{item.product_name}</h2>
<p>${Number(item.unit_price).toFixed(2)} each</p>
<strong>
Qty {item.quantity} · $
{(Number(item.unit_price) * item.quantity).toFixed(2)}
</strong>
</IonLabel>
</IonItem>
))}
</IonList>
</div>
</>
)
)}
</IonContent>
</IonPage>
);
};
export default OrderDetail;