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/lib/stores/ordersStore.ts
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

188 lines
5.8 KiB
TypeScript

import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { seedOrders } from "@/lib/data/seed";
import type { CartItem } from "@/lib/stores/cartStore";
import type { Fulfillment, PaymentMethod } from "@/lib/stores/locationStore";
export type OrderStatus =
| "requested"
| "confirmed"
| "paid"
| "dispatched"
| "delivered"
| "completed"
| "cancelled";
export type Order = {
id: string;
createdAt: string;
items: CartItem[];
subtotal: number;
deliveryFee: number;
discount: number;
voucherCode?: string | null;
total: number;
fulfillment: Fulfillment;
branchId?: string | null;
areaLabel?: string;
addressId?: string | null;
addressLine?: string;
paymentMethod: PaymentMethod;
status: OrderStatus;
riderAssigned?: boolean;
corporateCovered?: number;
selfPay?: number;
customerName?: string;
customerPhone?: string;
notes?: string;
/** Guest / pickup QR-style code shown at any branch counter */
pickupCode?: string | null;
/** When true, order can be fulfilled at any Gishen branch */
anyBranch?: boolean;
isGuestOrder?: boolean;
timeline?: { at: string; label: string; status: OrderStatus | "created" }[];
};
type OrdersState = {
orders: Order[];
addOrder: (
order: Omit<Order, "id" | "createdAt" | "status" | "timeline" | "discount"> & {
discount?: number;
status?: OrderStatus;
}
) => Order;
updateStatus: (id: string, status: OrderStatus) => void;
cancelOrder: (id: string) => void;
getById: (id: string) => Order | undefined;
getByPickupCode: (code: string) => Order | undefined;
};
function timelineFor(status: OrderStatus, createdAt: string) {
const steps: Order["timeline"] = [
{ at: createdAt, label: "Order placed", status: "created" },
];
const add = (label: string, s: OrderStatus, hours: number) => {
const d = new Date(createdAt);
d.setHours(d.getHours() + hours);
steps!.push({ at: d.toISOString(), label, status: s });
};
if (["confirmed", "paid", "dispatched", "delivered", "completed"].includes(status)) {
add("Confirmed by pharmacy", "confirmed", 0.2);
}
if (["paid", "dispatched", "delivered", "completed"].includes(status) && status !== "confirmed") {
add("Payment received", "paid", 0.4);
}
if (["dispatched", "delivered", "completed"].includes(status)) {
add("Out for delivery / ready for pickup", "dispatched", 1);
}
if (["delivered", "completed"].includes(status)) {
add("Delivered / collected", "delivered", 3);
}
if (status === "cancelled") {
steps.push({
at: new Date().toISOString(),
label: "Order cancelled",
status: "cancelled",
});
}
return steps;
}
export const useOrdersStore = create<OrdersState>()(
persist(
(set, get) => ({
orders: seedOrders.map((o) => ({
...o,
discount: o.discount ?? 0,
customerName: o.customerName ?? "Kirukib Abebe",
customerPhone: o.customerPhone ?? "+251911000000",
notes: o.notes ?? "",
timeline: o.timeline ?? timelineFor(o.status, o.createdAt),
})),
addOrder: (payload) => {
const createdAt = new Date().toISOString();
const discount = payload.discount ?? 0;
const status =
payload.status ??
(payload.paymentMethod === "pay_at_branch" ? "confirmed" : "paid");
const order: Order = {
...payload,
discount,
id: `ord_${Date.now()}`,
createdAt,
status,
riderAssigned: false,
timeline: timelineFor(status, createdAt),
};
set({ orders: [order, ...get().orders] });
return order;
},
updateStatus: (id, status) =>
set({
orders: get().orders.map((o) => {
if (o.id !== id) return o;
const next = {
...o,
status,
riderAssigned:
status === "dispatched" || status === "delivered"
? true
: o.riderAssigned,
};
const stamp = new Date().toISOString();
const label =
status === "dispatched"
? "Rider assigned / ready"
: status === "delivered"
? "Delivered / collected"
: status === "cancelled"
? "Order cancelled"
: `Status: ${status}`;
return {
...next,
timeline: [...(o.timeline ?? []), { at: stamp, label, status }],
};
}),
}),
cancelOrder: (id) => get().updateStatus(id, "cancelled"),
getById: (id) => get().orders.find((o) => o.id === id),
getByPickupCode: (code) => {
const normalized = code.trim().toUpperCase();
if (!normalized) return undefined;
return get().orders.find(
(o) => (o.pickupCode ?? "").toUpperCase() === normalized
);
},
}),
{
name: "gishen-orders",
storage: createJSONStorage(() => AsyncStorage),
version: 4,
migrate: (persisted: unknown) => {
const state = (persisted ?? {}) as Partial<OrdersState>;
if (!state.orders?.length) {
return {
...state,
orders: seedOrders.map((o) => ({
...o,
discount: o.discount ?? 0,
customerName: o.customerName ?? "Kirukib Abebe",
customerPhone: o.customerPhone ?? "+251911000000",
timeline: timelineFor(o.status, o.createdAt),
})),
};
}
return {
...state,
orders: state.orders.map((o) => ({
...o,
discount: o.discount ?? 0,
timeline: o.timeline ?? timelineFor(o.status, o.createdAt),
})),
};
},
}
)
);