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)/prescription/[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

212 lines
7.0 KiB
TypeScript

import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { 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 { products } from "@/lib/data/products";
import { useCartStore } from "@/lib/stores/cartStore";
import { usePrescriptionStore } from "@/lib/stores/prescriptionStore";
import { colors, gutter, radii } from "@/lib/theme";
import { routes } from "@/lib/routes";
const statusTone = {
submitted: "neutral",
under_review: "warn",
approved: "brand",
queried: "info",
rejected: "danger",
filled: "brand",
} as const;
const lineTone = {
ready: "brand",
review: "warn",
unavailable: "danger",
} as const;
export default function PrescriptionDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const rx = usePrescriptionStore((s) => s.prescriptions.find((p) => p.id === id));
const reminders = usePrescriptionStore((s) =>
s.reminders.filter((r) => r.prescriptionId === id)
);
const markRefilled = usePrescriptionStore((s) => s.markRefilled);
const updateRxStatus = usePrescriptionStore((s) => s.updateRxStatus);
const addProduct = useCartStore((s) => s.addProduct);
if (!rx) {
return (
<Screen>
<AppHeader title="Prescription" />
<View style={{ padding: gutter }}>
<Text variant="body" color={colors.muted}>
Prescription not found.
</Text>
</View>
</Screen>
);
}
const dueIn = (() => {
if (!rx.lastFilledAt) return null;
const due = new Date(rx.lastFilledAt);
due.setDate(due.getDate() + rx.daysSupply);
return Math.ceil((due.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
})();
return (
<Screen>
<AppHeader title="Prescription detail" />
<ScrollView
contentContainerStyle={{
paddingHorizontal: gutter,
paddingBottom: 40,
gap: 16,
}}
>
{rx.imageUris[0] ? (
<Image
source={{ uri: rx.imageUris[0] }}
style={{
width: "100%",
height: 180,
borderRadius: radii.card,
backgroundColor: colors.surfaceAlt,
}}
resizeMode="cover"
/>
) : null}
<Card>
<View style={{ flexDirection: "row", justifyContent: "space-between", gap: 12 }}>
<View style={{ flex: 1 }}>
<Text variant="h1">{rx.medicineName}</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
For {rx.patientName}
</Text>
</View>
<Badge
label={rx.status.replace("_", " ")}
tone={statusTone[rx.status]}
/>
</View>
{dueIn !== null ? (
<Text
variant="label"
color={dueIn <= 3 ? colors.warn : colors.brandText}
style={{ marginTop: 12 }}
>
{dueIn <= 0 ? "Refill overdue" : `Refill in ${dueIn} days`}
</Text>
) : (
<Text variant="label" color={colors.warn} style={{ marginTop: 12 }}>
Awaiting first fill
</Text>
)}
</Card>
<Section title="Medicines on this Rx">
<Card elevated={false}>
{rx.medicines.map((m, i) => (
<View
key={m.name}
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 12,
borderBottomWidth: i === rx.medicines.length - 1 ? 0 : 1,
borderBottomColor: colors.line,
}}
>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<Ionicons name="medkit-outline" size={18} color={colors.brandText} />
<Text variant="h3">{m.name}</Text>
</View>
<Badge label={m.status} tone={lineTone[m.status]} />
</View>
))}
</Card>
</Section>
<Section title="Details">
<Card elevated={false}>
<InfoRow label="Days supply" value={`${rx.daysSupply} days`} />
<InfoRow
label="Last filled"
value={
rx.lastFilledAt
? new Date(rx.lastFilledAt).toLocaleDateString()
: "Not filled yet"
}
/>
<InfoRow
label="Submitted"
value={new Date(rx.createdAt).toLocaleDateString()}
last={!rx.notes}
/>
{rx.notes ? <InfoRow label="Notes" value={rx.notes} last /> : null}
</Card>
</Section>
<Section title="Reminders">
{reminders.length === 0 ? (
<Card elevated={false}>
<Text variant="bodySm" color={colors.muted}>
No reminders yet. Set times so we can nudge you — and escalate if a dose is missed.
</Text>
</Card>
) : (
reminders.map((r) => (
<Card key={r.id} elevated={false} style={{ marginBottom: 8 }}>
<Text variant="h3">{r.medicineName}</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 4 }}>
{r.times.join(" · ")} · {r.graceMinutes} min grace
{r.escalateOnMiss ? " · escalate if missed" : ""}
</Text>
</Card>
))
)}
<Button
title="Manage calendar reminders"
variant="soft"
icon="calendar-outline"
onPress={() => router.push(routes.rxCalendar(rx.id))}
style={{ marginTop: 8 }}
/>
</Section>
<View style={{ gap: 10, marginTop: 8 }}>
<Button
title="Reorder medicines"
icon="bag-add-outline"
onPress={() => {
const match = products.find((p) =>
rx.medicineName.toLowerCase().includes(p.name.toLowerCase().split(" ")[0])
);
if (match) addProduct(match);
router.push(routes.cart);
}}
/>
<Button
title="Mark as refilled"
variant="outline"
onPress={() => markRefilled(rx.id)}
/>
{rx.status === "under_review" || rx.status === "submitted" ? (
<Button
title="Simulate pharmacist approval"
variant="soft"
onPress={() => updateRxStatus(rx.id, "approved")}
/>
) : null}
</View>
</ScrollView>
</Screen>
);
}