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/calendar/[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

284 lines
9.8 KiB
TypeScript

import { useLocalSearchParams } from "expo-router";
import { useMemo, useState } from "react";
import { Alert, ScrollView, View } from "react-native";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { Chip } from "@/components/ui/Chip";
import { Field, SwitchRow } from "@/components/ui/Field";
import { AppHeader } from "@/components/ui/Header";
import { EmptyState } from "@/components/ui/EmptyState";
import { Screen } from "@/components/ui/Screen";
import { Text } from "@/components/ui/Text";
import {
scheduleDailyDoseReminders,
scheduleDoseReminder,
scheduleEscalationCheck,
} from "@/lib/notifications";
import { useEmergencyContactStore } from "@/lib/stores/emergencyContactStore";
import { useNotificationStore } from "@/lib/stores/notificationStore";
import { usePrescriptionStore } from "@/lib/stores/prescriptionStore";
import { colors, gutter } from "@/lib/theme";
import { routes } from "@/lib/routes";
const PRESETS = ["08:00", "12:00", "14:00", "18:00", "20:00"] as const;
export default function RxCalendarScreen() {
const params = useLocalSearchParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const rx = usePrescriptionStore((s) => s.prescriptions.find((p) => p.id === id));
const upsertReminder = usePrescriptionStore((s) => s.upsertReminder);
const schedulePendingDose = usePrescriptionStore((s) => s.schedulePendingDose);
const confirmDose = usePrescriptionStore((s) => s.confirmDose);
const escalateDose = usePrescriptionStore((s) => s.escalateDose);
const doseLogs = usePrescriptionStore((s) => s.doseLogs);
const reminder = usePrescriptionStore((s) =>
s.reminders.find((r) => r.prescriptionId === id)
);
const grace = useEmergencyContactStore((s) => s.graceMinutesDefault);
const setGrace = useEmergencyContactStore((s) => s.setGrace);
const escalationEnabled = useEmergencyContactStore((s) => s.escalationEnabled);
const setEscalationEnabled = useEmergencyContactStore((s) => s.setEscalationEnabled);
const contacts = useEmergencyContactStore((s) => s.contacts);
const markEscalated = useEmergencyContactStore((s) => s.markEscalated);
const pushNotification = useNotificationStore((s) => s.push);
const [times, setTimes] = useState(reminder?.times.join(",") || "08:00,20:00");
const [pendingLogId, setPendingLogId] = useState<string | null>(null);
const reminderId = `rem_${id}`;
const relatedLogs = useMemo(
() => doseLogs.filter((l) => l.reminderId === reminderId).slice(0, 8),
[doseLogs, reminderId]
);
if (!rx) {
return (
<Screen>
<EmptyState icon="calendar-outline" title="Prescription not found" />
</Screen>
);
}
const togglePreset = (t: string) => {
const list = times
.split(",")
.map((s) => s.trim())
.filter(Boolean);
if (list.includes(t)) {
setTimes(list.filter((x) => x !== t).join(","));
} else {
setTimes([...list, t].sort().join(","));
}
};
const saveAndSchedule = async () => {
const parsed = times
.split(",")
.map((s) => s.trim())
.filter(Boolean);
if (!parsed.length) {
Alert.alert("Times required", "Pick at least one reminder time.");
return;
}
upsertReminder({
id: reminderId,
prescriptionId: rx.id,
medicineName: rx.medicineName,
times: parsed,
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
graceMinutes: grace,
escalateOnMiss: escalationEnabled,
enabled: true,
});
const result = await scheduleDailyDoseReminders({
prescriptionId: rx.id,
medicineName: rx.medicineName,
times: parsed,
});
const log = schedulePendingDose(reminderId);
setPendingLogId(log.id);
pushNotification({
kind: "dose",
title: `Calendar set · ${rx.medicineName}`,
body: result.granted
? `Daily push at ${result.times.join(", ")}`
: `Saved ${result.times.join(", ")} — enable notifications for push`,
href: routes.rxCalendar(rx.id),
});
if (escalationEnabled && contacts.length) {
await scheduleEscalationCheck({
title: "Dose not confirmed",
body: `Notifying ${contacts[0].name} if still unconfirmed`,
graceSeconds: Math.max(20, grace * 60),
data: { logId: log.id, type: "escalation" },
});
setTimeout(() => {
const current = usePrescriptionStore
.getState()
.doseLogs.find((l) => l.id === log.id);
if (current?.status === "pending") {
escalateDose(log.id);
markEscalated();
Alert.alert("Emergency", `${contacts[0].name} would be notified (mock SMS/push)`);
}
}, Math.max(25000, grace * 1000));
} else if (escalationEnabled && !contacts.length) {
Alert.alert(
"No emergency contact",
"Add a contact under Profile → Emergency contacts for escalation."
);
}
Alert.alert(
result.granted ? "Scheduled" : "Saved",
result.granted
? `Daily calendar push at ${result.times.join(", ")}. Demo ping in ~12s.`
: `Reminders saved at ${result.times.join(", ")}. Allow notifications for daily push.`
);
};
return (
<Screen>
<AppHeader title="Reminders" subtitle={rx.medicineName} />
<ScrollView
contentContainerStyle={{
paddingHorizontal: gutter,
paddingBottom: 40,
gap: 14,
}}
>
<Card>
<Text variant="caption" color={colors.muted}>
Patient
</Text>
<Text variant="h3" style={{ marginTop: 4 }}>
{rx.patientName || "Self"}
</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 4 }}>
{rx.status} · {rx.daysSupply} day supply
</Text>
</Card>
<Card>
<Text variant="h3">Dose times</Text>
<Text variant="caption" color={colors.muted} style={{ marginTop: 4 }}>
Tap presets or edit the comma-separated list
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }}>
{PRESETS.map((p) => {
const selected = times
.split(",")
.map((s) => s.trim())
.includes(p);
return (
<Chip
key={p}
label={p}
selected={selected}
onPress={() => togglePreset(p)}
size="sm"
/>
);
})}
</View>
<View style={{ marginTop: 12 }}>
<Field
label="Times (HH:mm)"
value={times}
onChangeText={setTimes}
hint="Example: 08:00,14:00,20:00"
/>
</View>
</Card>
<Card padded={false}>
<SwitchRow
icon="alert-circle-outline"
title="Escalate if missed"
subtitle={
contacts[0]
? `Notify ${contacts[0].name} after grace period`
: "Add an emergency contact first"
}
value={escalationEnabled}
onValueChange={setEscalationEnabled}
/>
<View style={{ padding: 16, paddingTop: 4 }}>
<Field
label="Grace minutes"
value={String(grace)}
keyboardType="number-pad"
onChangeText={(v) => setGrace(Number(v) || 30)}
/>
</View>
</Card>
{pendingLogId ? (
<Card tone="brand" elevated={false}>
<Text variant="h3">Confirm this dose</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 4 }}>
Demo reminder is active. Mark taken or snooze.
</Text>
<View style={{ flexDirection: "row", gap: 10, marginTop: 14 }}>
<Button
title="Taken"
style={{ flex: 1 }}
onPress={() => {
confirmDose(pendingLogId);
setPendingLogId(null);
Alert.alert("Logged", "Dose confirmed");
}}
/>
<Button
title="Snooze 10m"
variant="outline"
style={{ flex: 1 }}
onPress={() =>
scheduleDoseReminder({
title: `Snoozed: ${rx.medicineName}`,
body: "Reminder",
secondsFromNow: 600,
})
}
/>
</View>
</Card>
) : null}
<Button title="Save & schedule push" onPress={saveAndSchedule} />
<Text variant="h3">Recent dose logs</Text>
{relatedLogs.length === 0 && doseLogs.length === 0 ? (
<Text variant="bodySm" color={colors.muted}>
No logs yet. Schedule a reminder to start tracking.
</Text>
) : (
(relatedLogs.length ? relatedLogs : doseLogs.slice(0, 8)).map((l) => (
<Card key={l.id} elevated={false}>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<Badge
label={l.status.replace("_", " ")}
tone={
l.status === "taken"
? "brand"
: l.status === "missed_escalated"
? "danger"
: l.status === "pending"
? "warn"
: "neutral"
}
/>
<Text variant="bodySm" color={colors.muted} style={{ flex: 1 }}>
{new Date(l.scheduledAt).toLocaleString()}
</Text>
</View>
</Card>
))
)}
</ScrollView>
</Screen>
);
}