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 7310a349c5 Add Gishen mobile app with complete demo shopping and care flows.
Ship Expo Router screens, seeded stores, and polished UX so auth, shop, checkout, prescriptions, loyalty, family, and account processes work end-to-end offline.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 01:21:32 +03:00

241 lines
8.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}
</Text>
<Text variant="h2" style={{ marginTop: 4 }}>
{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>
<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}
{branch ? (
<InfoRow label="Branch" value={`${branch.name} · ${branch.hours}`} last />
) : (
<InfoRow label="Branch" value="—" 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>
);
}