This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Mob/app/(root)/(screens)/orders/[id].tsx
kirukib 5391780b9b Ship Rx Care tab, notifications inbox, and guest pickup codes.
Replace the profile tab with prescription management (inline reminders and family invite), add a shared tab top bar, separate notifications from orders, and support any-branch guest checkout with GSH codes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 15:35:51 +03:00

269 lines
9.1 KiB
TypeScript

import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { Alert, Image, ScrollView, View } from "react-native";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Card, InfoRow, Section } from "@/components/ui/Card";
import { AppHeader } from "@/components/ui/Header";
import { Screen } from "@/components/ui/Screen";
import { Text } from "@/components/ui/Text";
import { getBranchById } from "@/lib/data/branches";
import { routes } from "@/lib/routes";
import { useOrdersStore } from "@/lib/stores/ordersStore";
import { colors, gutter, radii } from "@/lib/theme";
import { formatETB } from "@/lib/utils";
const toneFor = {
requested: "neutral",
confirmed: "info",
paid: "brand",
dispatched: "warn",
delivered: "brand",
completed: "brand",
cancelled: "danger",
} as const;
export default function OrderDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const order = useOrdersStore((s) => s.getById(id));
const cancelOrder = useOrdersStore((s) => s.cancelOrder);
const updateStatus = useOrdersStore((s) => s.updateStatus);
if (!order) {
return (
<Screen>
<AppHeader title="Order" />
<View style={{ padding: gutter }}>
<Text variant="body" color={colors.muted}>
Order not found.
</Text>
</View>
</Screen>
);
}
const branch = order.branchId ? getBranchById(order.branchId) : undefined;
const canCancel = !["delivered", "completed", "cancelled"].includes(order.status);
return (
<Screen>
<AppHeader
title="Order details"
subtitle={new Date(order.createdAt).toLocaleString()}
right={
<Badge label={order.status} tone={toneFor[order.status]} />
}
/>
<ScrollView
contentContainerStyle={{
paddingHorizontal: gutter,
paddingBottom: 40,
gap: 14,
}}
>
<Card>
<Text variant="caption" color={colors.subtle}>
{order.id}
{order.isGuestOrder ? " · Guest" : ""}
</Text>
<Text variant="h2" style={{ marginTop: 4 }}>
{order.anyBranch
? "Any-branch pickup"
: order.fulfillment === "pickup"
? "Pickup order"
: "Delivery order"}
</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
{order.customerName} · {order.customerPhone}
</Text>
{order.notes ? (
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
Notes: {order.notes}
</Text>
) : null}
</Card>
{order.pickupCode ? (
<Card tone="brand" elevated={false}>
<Text variant="caption" color={colors.muted}>
Show this at the branch counter
</Text>
<Text variant="display" style={{ marginTop: 6, letterSpacing: 2 }}>
{order.pickupCode}
</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 8 }}>
{order.anyBranch
? "Valid at any Gishen Pharmacy branch for fulfillment."
: `Pickup at ${branch?.name ?? "selected branch"}.`}
</Text>
</Card>
) : null}
<Section title="Items">
<Card elevated={false}>
{order.items.map((item, index) => (
<View
key={item.productId}
style={{
flexDirection: "row",
gap: 12,
paddingVertical: 12,
borderBottomWidth: index === order.items.length - 1 ? 0 : 1,
borderBottomColor: colors.line,
}}
>
<Image
source={{ uri: item.image }}
style={{
width: 52,
height: 52,
borderRadius: radii.sm,
backgroundColor: colors.surfaceAlt,
}}
resizeMode="contain"
/>
<View style={{ flex: 1 }}>
<Text variant="h3">{item.name}</Text>
<Text variant="caption" color={colors.subtle} style={{ marginTop: 2 }}>
{item.unit} · Qty {item.quantity}
{item.requiresPrescription ? " · RX" : ""}
</Text>
</View>
<Text variant="label">{formatETB(item.price * item.quantity)}</Text>
</View>
))}
</Card>
</Section>
<Section title="Fulfillment">
<Card elevated={false}>
<InfoRow
label="Method"
value={order.fulfillment === "pickup" ? "Pickup" : "Delivery"}
/>
<InfoRow label="Area" value={order.areaLabel || "—"} />
{order.addressLine ? (
<InfoRow label="Address" value={order.addressLine} />
) : null}
{order.pickupCode ? (
<InfoRow label="Pickup code" value={order.pickupCode} />
) : null}
{branch ? (
<InfoRow label="Branch" value={`${branch.name} · ${branch.hours}`} last />
) : (
<InfoRow
label="Branch"
value={order.anyBranch ? "Any Gishen branch" : "—"}
last
/>
)}
</Card>
</Section>
<Section title="Payment summary">
<Card elevated={false}>
<InfoRow label="Subtotal" value={formatETB(order.subtotal)} />
{(order.discount ?? 0) > 0 ? (
<InfoRow
label={`Discount${order.voucherCode ? ` (${order.voucherCode})` : ""}`}
value={`- ${formatETB(order.discount)}`}
valueColor={colors.brandText}
/>
) : null}
<InfoRow
label="Delivery fee"
value={order.deliveryFee ? formatETB(order.deliveryFee) : "Free"}
/>
{(order.corporateCovered ?? 0) > 0 ? (
<InfoRow
label="Corporate covered"
value={formatETB(order.corporateCovered!)}
valueColor={colors.brandText}
/>
) : null}
<InfoRow label="Payment method" value={order.paymentMethod} />
<InfoRow
label="Total"
value={formatETB(order.total)}
emphasis
last
/>
</Card>
</Section>
<Section title="Status timeline">
<Card elevated={false}>
{(order.timeline ?? []).map((step, index, arr) => (
<View
key={`${step.at}-${step.label}`}
style={{ flexDirection: "row", gap: 12, paddingVertical: 8 }}
>
<View style={{ alignItems: "center", width: 18 }}>
<View
style={{
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: colors.brand,
marginTop: 4,
}}
/>
{index < arr.length - 1 ? (
<View
style={{
width: 2,
flex: 1,
backgroundColor: colors.brandBorder,
marginTop: 2,
}}
/>
) : null}
</View>
<View style={{ flex: 1, paddingBottom: 8 }}>
<Text variant="h3">{step.label}</Text>
<Text variant="caption" color={colors.subtle} style={{ marginTop: 2 }}>
{new Date(step.at).toLocaleString()}
</Text>
</View>
</View>
))}
</Card>
</Section>
<View style={{ gap: 10 }}>
{order.status !== "cancelled" && order.status !== "delivered" ? (
<Button
title="Track order"
icon="navigate-outline"
onPress={() => router.push(routes.tracking(order.id))}
/>
) : null}
{order.status === "confirmed" || order.status === "paid" ? (
<Button
title="Simulate dispatch"
variant="soft"
onPress={() => updateStatus(order.id, "dispatched")}
/>
) : null}
{canCancel ? (
<Button
title="Cancel order"
variant="danger"
onPress={() =>
Alert.alert("Cancel order?", "This cannot be undone in the demo.", [
{ text: "Keep", style: "cancel" },
{
text: "Cancel order",
style: "destructive",
onPress: () => cancelOrder(order.id),
},
])
}
/>
) : null}
</View>
</ScrollView>
</Screen>
);
}