eletrotupi / tcc/ frontend/components/medicine/TodayMedicines.tsxa13a1c0382a8d1ae8d553f0a91d6e5716c967b31
4.8 KB | 147 lines Raw
1
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from "react-native";
2
import { Ionicons } from "@expo/vector-icons";
3
import { Colors, Typography, Spacing, BorderRadius, Shadows } from "@/constants/theme";
4
import { useMedicineToday, useCreateCareAction, medicineTodayKeys } from "@/hooks";
5
import { useQueryClient } from "@tanstack/react-query";
6
import { TodayMedicineEntry } from "@/lib/api";
7

8
function sortedTimes(scheduledAt: string[]): string[] {
9
  return [...scheduledAt].sort((a, b) => {
10
    const [ah, am] = a.split(":").map(Number);
11
    const [bh, bm] = b.split(":").map(Number);
12
    return ah * 60 + am - (bh * 60 + bm);
13
  });
14
}
15

16
function confirmedIndexes(entry: TodayMedicineEntry): Set<number> {
17
  const confirmed = new Set<number>();
18
  const n = Math.min(entry.logs.length, entry.regimen.scheduledAt.length);
19
  for (let i = 0; i < n; i++) confirmed.add(i);
20
  return confirmed;
21
}
22

23
export function TodayMedicines() {
24
  const { data, isLoading } = useMedicineToday();
25
  const createCareAction = useCreateCareAction();
26
  const queryClient = useQueryClient();
27

28
  if (isLoading) {
29
    return (
30
      <View style={styles.center}>
31
        <ActivityIndicator color={Colors.light.tint} />
32
      </View>
33
    );
34
  }
35

36
  const entries = data?.regimens ?? [];
37

38
  if (entries.length === 0) {
39
    return (
40
      <View style={styles.empty}>
41
        <Text style={styles.emptyText}>Nenhum medicamento agendado para hoje</Text>
42
      </View>
43
    );
44
  }
45

46
  const handleConfirm = async (regimenId: number) => {
47
    await createCareAction.mutateAsync({
48
      type: "MEDICINE",
49
      moment: new Date(),
50
      regimenId,
51
    });
52
    queryClient.invalidateQueries({ queryKey: medicineTodayKeys.list() });
53
  };
54

55
  return (
56
    <View style={styles.container}>
57
      {entries.map((entry) => {
58
        const times = entry.regimen.scheduledAt.length > 0
59
          ? sortedTimes(entry.regimen.scheduledAt)
60
          : ["hoje"];
61
        const confirmed = confirmedIndexes(entry);
62

63
        return (
64
          <View key={entry.regimen.id} style={styles.card}>
65
            <View style={styles.cardHeader}>
66
              <View style={styles.iconWrap}>
67
                <Ionicons name="bandage-outline" size={18} color={Colors.light.tint} />
68
              </View>
69
              <View style={styles.cardInfo}>
70
                <Text style={styles.name}>{entry.regimen.name}</Text>
71
                <Text style={styles.dosage}>{entry.regimen.dosage}</Text>
72
              </View>
73
            </View>
74

75
            {times.map((time, idx) => {
76
              const isDone = time === "hoje" ? entry.logs.length > 0 : confirmed.has(idx);
77

78
              return (
79
                <TouchableOpacity
80
                  key={time}
81
                  style={[styles.timeRow, isDone && styles.timeRowConfirmed]}
82
                  onPress={isDone ? undefined : () => handleConfirm(entry.regimen.id)}
83
                  activeOpacity={isDone ? 1 : 0.7}
84
                  disabled={isDone || createCareAction.isPending}
85
                >
86
                  <Text style={[styles.timeText, isDone && styles.timeTextConfirmed]}>
87
                    {time === "hoje" ? "Hoje" : time}
88
                  </Text>
89
                  {isDone ? (
90
                    <Ionicons name="checkmark-circle" size={20} color="#34C759" />
91
                  ) : (
92
                    <View style={styles.pendingCircle} />
93
                  )}
94
                </TouchableOpacity>
95
              );
96
            })}
97
          </View>
98
        );
99
      })}
100
    </View>
101
  );
102
}
103

104
const styles = StyleSheet.create({
105
  container: { gap: Spacing.cardGap },
106
  center: { padding: Spacing.cardGap, alignItems: "center" },
107
  empty: { padding: Spacing.cardGap },
108
  emptyText: { ...Typography.bodyMd, color: Colors.light.textSecondary },
109
  card: {
110
    backgroundColor: "#fff",
111
    borderRadius: BorderRadius.lg,
112
    padding: Spacing.cardGap,
113
    gap: 10,
114
    ...Shadows.sm,
115
  },
116
  cardHeader: { flexDirection: "row", alignItems: "center", gap: 10 },
117
  iconWrap: {
118
    width: 32,
119
    height: 32,
120
    borderRadius: BorderRadius.md,
121
    backgroundColor: Colors.light.tint + "1A",
122
    alignItems: "center",
123
    justifyContent: "center",
124
  },
125
  cardInfo: { flex: 1 },
126
  name: { ...Typography.bodyMd, fontWeight: "600" },
127
  dosage: { ...Typography.labelSm, color: Colors.light.textSecondary },
128
  timeRow: {
129
    flexDirection: "row",
130
    alignItems: "center",
131
    justifyContent: "space-between",
132
    paddingVertical: 8,
133
    paddingHorizontal: 12,
134
    borderRadius: BorderRadius.md,
135
    backgroundColor: Colors.light.background,
136
  },
137
  timeRowConfirmed: { backgroundColor: "#F0FFF4", opacity: 0.8 },
138
  timeText: { ...Typography.bodyMd, fontWeight: "500" },
139
  timeTextConfirmed: { color: Colors.light.textSecondary },
140
  pendingCircle: {
141
    width: 20,
142
    height: 20,
143
    borderRadius: 10,
144
    borderWidth: 2,
145
    borderColor: Colors.light.divider,
146
  },
147
});