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/components/ui/DoseReminderPanel.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

203 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useMemo, useState } from "react";
import { Alert, View } from "react-native";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Chip } from "@/components/ui/Chip";
import { SwitchRow } from "@/components/ui/Field";
import { Text } from "@/components/ui/Text";
import { scheduleDailyDoseReminders } from "@/lib/notifications";
import { useEmergencyContactStore } from "@/lib/stores/emergencyContactStore";
import { useNotificationStore } from "@/lib/stores/notificationStore";
import {
usePrescriptionStore,
type Prescription,
type Reminder,
} from "@/lib/stores/prescriptionStore";
import { colors, radii } from "@/lib/theme";
import { routes } from "@/lib/routes";
const PRESETS = ["08:00", "12:00", "14:00", "18:00", "20:00"] as const;
export function DoseReminderPanel({
rx,
reminder,
compact,
onScheduled,
}: {
rx: Prescription;
reminder?: Reminder | null;
compact?: boolean;
onScheduled?: () => void;
}) {
const upsertReminder = usePrescriptionStore((s) => s.upsertReminder);
const schedulePendingDose = usePrescriptionStore((s) => s.schedulePendingDose);
const grace = useEmergencyContactStore((s) => s.graceMinutesDefault);
const escalationEnabled = useEmergencyContactStore((s) => s.escalationEnabled);
const setEscalationEnabled = useEmergencyContactStore((s) => s.setEscalationEnabled);
const contacts = useEmergencyContactStore((s) => s.contacts);
const pushNotification = useNotificationStore((s) => s.push);
const [times, setTimes] = useState(
reminder?.times?.length ? reminder.times.join(",") : "08:00,20:00"
);
const [busy, setBusy] = useState(false);
const selected = useMemo(
() =>
times
.split(",")
.map((s) => s.trim())
.filter(Boolean),
[times]
);
const togglePreset = (t: string) => {
if (selected.includes(t)) {
setTimes(selected.filter((x) => x !== t).join(","));
} else {
setTimes([...selected, t].sort().join(","));
}
};
const save = async () => {
if (!selected.length) {
Alert.alert("Pick a time", "Choose at least one dose time.");
return;
}
setBusy(true);
try {
const reminderId = `rem_${rx.id}`;
upsertReminder({
id: reminderId,
prescriptionId: rx.id,
medicineName: rx.medicineName,
times: selected,
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
graceMinutes: grace,
escalateOnMiss: escalationEnabled,
enabled: true,
memberId: rx.memberId,
});
const result = await scheduleDailyDoseReminders({
prescriptionId: rx.id,
medicineName: rx.medicineName,
times: selected,
});
schedulePendingDose(reminderId);
pushNotification({
kind: "dose",
title: `Reminders on · ${rx.medicineName}`,
body: result.granted
? `Daily push at ${result.times.join(", ")}. Demo ping in ~12s.`
: `Saved ${result.times.join(", ")}. Enable notification permission for push.`,
href: routes.rxCalendar(rx.id),
});
Alert.alert(
result.granted ? "Reminders scheduled" : "Reminders saved",
result.granted
? `Calendar push notifications set for ${result.times.join(", ")}. You'll get a demo notification shortly.`
: `Times saved (${result.times.join(", ")}). Allow notifications to receive daily push alerts.`
);
onScheduled?.();
} finally {
setBusy(false);
}
};
return (
<View
style={
compact
? {
marginTop: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: colors.line,
gap: 10,
}
: { gap: 12 }
}
>
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
<Text variant="h3" style={{ flex: 1 }}>
Dose times
</Text>
{reminder?.enabled ? <Badge label="Active" tone="brand" /> : <Badge label="Not set" tone="neutral" />}
</View>
{!compact ? (
<Text variant="caption" color={colors.muted}>
Pick daily times. We’ll schedule calendar push notifications for each slot.
</Text>
) : null}
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
{PRESETS.map((p) => (
<Chip
key={p}
label={p}
selected={selected.includes(p)}
onPress={() => togglePreset(p)}
size="sm"
/>
))}
</View>
{selected.length ? (
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 6,
padding: 10,
borderRadius: radii.md,
backgroundColor: colors.brandWash,
borderWidth: 1,
borderColor: colors.brandBorder,
}}
>
<Text variant="caption" color={colors.brandText}>
Schedule: {selected.join(" · ")} every day
</Text>
</View>
) : null}
{!compact ? (
<View
style={{
borderRadius: radii.md,
borderWidth: 1,
borderColor: colors.line,
overflow: "hidden",
backgroundColor: colors.surface,
}}
>
<SwitchRow
icon="notifications-outline"
title="Escalate if missed"
subtitle={
contacts[0]
? `Notify ${contacts[0].name} after grace`
: "Add an emergency contact in profile"
}
value={escalationEnabled}
onValueChange={setEscalationEnabled}
last
/>
</View>
) : null}
<Button
title={busy ? "Scheduling…" : "Save & enable push"}
icon="alarm-outline"
onPress={save}
disabled={busy}
size={compact ? "md" : "lg"}
/>
</View>
);
}