| 1 | import { View, StyleSheet } from "react-native";
|
| 2 | import { router, type Href } from "expo-router";
|
| 3 | import { MaterialIcons } from "@expo/vector-icons";
|
| 4 |
|
| 5 | import { Text } from "@/components/ui/Text";
|
| 6 | import { Card } from "@/components/ui/Cards";
|
| 7 | import { Badge } from "@/components/ui/Badge";
|
| 8 | import { NOTIFICATION_KINDS } from "@/constants/notification-kinds";
|
| 9 | import { formatMoment } from "@/lib/utils/time";
|
| 10 | import { Spacing } from "@/constants/theme";
|
| 11 | import type { Notification } from "@/lib/api";
|
| 12 |
|
| 13 | type Props = {
|
| 14 | notification: Notification;
|
| 15 | };
|
| 16 |
|
| 17 | // A row is history once its relevance window has closed; a notification with no
|
| 18 | // window (an insight alert) never expires
|
| 19 | const isExpired = (notification: Notification): boolean =>
|
| 20 | notification.expiresAt !== null &&
|
| 21 | new Date(notification.expiresAt).getTime() <= Date.now();
|
| 22 |
|
| 23 | // The same deep-link shape the push observer uses: a pathname plus params
|
| 24 | function destination(notification: Notification): Href {
|
| 25 | if (notification.kind === "MEDICINE_REMINDER" && notification.regimenId !== null) {
|
| 26 | return {
|
| 27 | pathname: "/medicine-regimens/[id]",
|
| 28 | params: { id: String(notification.regimenId) },
|
| 29 | } as Href;
|
| 30 | }
|
| 31 |
|
| 32 | if (notification.kind === "MEDICINE_REMINDER") {
|
| 33 | return { pathname: "/medicine-regimens" } as Href;
|
| 34 | }
|
| 35 |
|
| 36 | if (notification.kind === "DAILY_REMINDER") {
|
| 37 | return { pathname: "/new/entry" } as Href;
|
| 38 | }
|
| 39 |
|
| 40 | return { pathname: "/insights" } as Href;
|
| 41 | }
|
| 42 |
|
| 43 | export function NotificationRow({ notification }: Props) {
|
| 44 | const meta = NOTIFICATION_KINDS[notification.kind];
|
| 45 | const expired = isExpired(notification);
|
| 46 |
|
| 47 | return (
|
| 48 | <Card
|
| 49 | style={styles.card}
|
| 50 | testID={`notification-${notification.id}`}
|
| 51 | onPress={() => router.push(destination(notification))}
|
| 52 | >
|
| 53 | <View style={[styles.row, expired && styles.expired]}>
|
| 54 | <View style={[styles.iconWrap, { backgroundColor: meta.iconBackground }]}>
|
| 55 | <MaterialIcons name={meta.icon} size={22} color={meta.color} />
|
| 56 | </View>
|
| 57 |
|
| 58 | <View style={styles.body}>
|
| 59 | <View style={styles.titleRow}>
|
| 60 | <Text style={styles.title} numberOfLines={1}>
|
| 61 | {notification.title}
|
| 62 | </Text>
|
| 63 |
|
| 64 | {expired && <Badge label="Expirada" variant="gray" />}
|
| 65 | </View>
|
| 66 |
|
| 67 | <Text style={styles.summary} numberOfLines={2}>
|
| 68 | {notification.body}
|
| 69 | </Text>
|
| 70 |
|
| 71 | <Text style={styles.moment}>
|
| 72 | {formatMoment(new Date(notification.scheduledFor))}
|
| 73 | </Text>
|
| 74 | </View>
|
| 75 | </View>
|
| 76 | </Card>
|
| 77 | );
|
| 78 | }
|
| 79 |
|
| 80 | const styles = StyleSheet.create({
|
| 81 | card: {
|
| 82 | padding: Spacing.cardGap,
|
| 83 | },
|
| 84 | row: {
|
| 85 | flexDirection: "row",
|
| 86 | alignItems: "flex-start",
|
| 87 | gap: 12,
|
| 88 | },
|
| 89 | expired: {
|
| 90 | opacity: 0.55,
|
| 91 | },
|
| 92 | iconWrap: {
|
| 93 | width: 40,
|
| 94 | height: 40,
|
| 95 | borderRadius: 20,
|
| 96 | alignItems: "center",
|
| 97 | justifyContent: "center",
|
| 98 | marginTop: 1,
|
| 99 | },
|
| 100 | body: {
|
| 101 | flex: 1,
|
| 102 | },
|
| 103 | titleRow: {
|
| 104 | flexDirection: "row",
|
| 105 | justifyContent: "space-between",
|
| 106 | alignItems: "center",
|
| 107 | gap: 8,
|
| 108 | marginBottom: 2,
|
| 109 | },
|
| 110 | title: {
|
| 111 | flexShrink: 1,
|
| 112 | fontSize: 15,
|
| 113 | fontWeight: "600",
|
| 114 | },
|
| 115 | summary: {
|
| 116 | fontSize: 13,
|
| 117 | opacity: 0.6,
|
| 118 | lineHeight: 18,
|
| 119 | },
|
| 120 | moment: {
|
| 121 | fontSize: 12,
|
| 122 | opacity: 0.45,
|
| 123 | marginTop: 4,
|
| 124 | },
|
| 125 | });
|