eletrotupi / tcc/ commit / d7eb725

frontend: rework the edit medicine screen

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago d7eb725e76fd8e9ebaab8530632e3dad82c3bf9a
Parents: ca6234d
1 file(s) changed
  • frontend/app/medicine-regimens/[id].tsx +223 -14
frontend/app/medicine-regimens/[id].tsx
1 -
import { View, Text, StyleSheet } from "react-native";
2 -
import { useLocalSearchParams } from "expo-router";
3 -
import { Spacing, Typography, Colors } from "@/constants/theme";
1 +
import {
2 +
  View,
3 +
  Text,
4 +
  StyleSheet,
5 +
  KeyboardAvoidingView,
6 +
  ScrollView,
7 +
  TouchableOpacity,
8 +
  ActivityIndicator,
9 +
} from "react-native";
10 +
import { useEffect, useState } from "react";
11 +
import { router, useLocalSearchParams } from "expo-router";
12 +
import { Card } from "@/components/ui/Cards";
13 +
import { Input } from "@/components/ui/Input";
14 +
import { Button } from "@/components/ui/Button";
15 +
import { Section, SectionHeader } from "@/components/ui/Sections";
16 +
import { Spacing, Typography, Colors, BorderRadius } from "@/constants/theme";
17 +
import { TimePicker } from "@/components/ui/TimePicker";
18 +
import { useMedicineRegimen, useUpdateMedicineRegimen } from "@/hooks";
19 +
import { MedicinePeriodicity } from "@/lib/api";
4 20

5 -
export default function MedicineRegimenDetail() {
21 +
const PERIODICITY_OPTIONS: { label: string; value: MedicinePeriodicity }[] = [
22 +
  { label: "Uma vez", value: "ONCE" },
23 +
  { label: "Diário", value: "DAILY" },
24 +
  { label: "2x ao dia", value: "TWICE_DAILY" },
25 +
  { label: "3x ao dia", value: "THREE_TIMES_DAILY" },
26 +
  { label: "Semanal", value: "WEEKLY" },
27 +
  { label: "Quinzenal", value: "BIWEEKLY" },
28 +
  { label: "Mensal", value: "MONTHLY" },
29 +
];
30 +

31 +
function getTimePickerCount(p: MedicinePeriodicity): number {
32 +
  switch (p) {
33 +
    case "ONCE": return 0;
34 +
    case "DAILY": return 1;
35 +
    case "TWICE_DAILY": return 2;
36 +
    case "THREE_TIMES_DAILY": return 3;
37 +
    default: return 1;
38 +
  }
39 +
}
40 +

41 +
function timesToDates(times: string[]): Date[] {
42 +
  return times.map((t) => {
43 +
    const [h, m] = t.split(":").map(Number);
44 +
    const d = new Date();
45 +
    d.setHours(h, m, 0, 0);
46 +
    return d;
47 +
  });
48 +
}
49 +

50 +
export default function MedicineRegimenEdit() {
6 51
  const { id } = useLocalSearchParams<{ id: string }>();
52 +
  const { data, isLoading } = useMedicineRegimen(id);
53 +
  const updateRegimen = useUpdateMedicineRegimen();
54 +

55 +
  const [name, setName] = useState("");
56 +
  const [dosage, setDosage] = useState("");
57 +
  const [periodicity, setPeriodicity] = useState<MedicinePeriodicity>("DAILY");
58 +
  const [scheduledTimes, setScheduledTimes] = useState<Date[]>([]);
59 +
  const [initialized, setInitialized] = useState(false);
60 +

61 +
  useEffect(() => {
62 +
    if (data?.regimen && !initialized) {
63 +
      const r = data.regimen;
64 +
      setName(r.name);
65 +
      setDosage(r.dosage);
66 +
      setPeriodicity(r.periodicity as MedicinePeriodicity);
67 +
      setScheduledTimes(timesToDates(r.scheduledAt));
68 +
      setInitialized(true);
69 +
    }
70 +
  }, [data, initialized]);
71 +

72 +
  const handleSave = async () => {
73 +
    const count = getTimePickerCount(periodicity);
74 +
    const scheduledAt = scheduledTimes.slice(0, count).map(
75 +
      (d) =>
76 +
        `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`
77 +
    );
78 +

79 +
    await updateRegimen.mutateAsync({
80 +
      id,
81 +
      payload: { name, dosage, periodicity, scheduledAt },
82 +
    });
83 +
    router.back();
84 +
  };
85 +

86 +
  if (isLoading || !initialized) {
87 +
    return (
88 +
      <View style={styles.center}>
89 +
        <ActivityIndicator color={Colors.light.tint} />
90 +
      </View>
91 +
    );
92 +
  }
7 93

8 94
  return (
9 -
    <View style={styles.container}>
10 -
      <Text style={styles.title}>Medicamento #{id}</Text>
11 -
      <Text style={styles.subtitle}>Em breve: edição de medicamento</Text>
12 -
    </View>
95 +
    <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
96 +
      <ScrollView scrollEventThrottle={16}>
97 +
        <View style={styles.container}>
98 +
          <View style={styles.header}>
99 +
            <Text style={styles.headerTitle}>Editar medicamento</Text>
100 +
            <Text style={styles.headerSubtitle}>
101 +
              Atualize os detalhes do medicamento
102 +
            </Text>
103 +
          </View>
104 +

105 +
          <Section>
106 +
            <SectionHeader title="Detalhes" />
107 +
            <Card style={{ padding: Spacing.cardGap }}>
108 +
              <Input
109 +
                label="Nome"
110 +
                value={name}
111 +
                onChangeText={setName}
112 +
                placeholder="Ex: Fluoxetina"
113 +
                variant="darkGhost"
114 +
              />
115 +
              <Input
116 +
                label="Dosagem"
117 +
                value={dosage}
118 +
                onChangeText={setDosage}
119 +
                placeholder="Ex: 20mg"
120 +
                variant="darkGhost"
121 +
              />
122 +
            </Card>
123 +
          </Section>
124 +

125 +
          <Section>
126 +
            <SectionHeader title="Frequência" />
127 +
            <View style={styles.periodicityRow}>
128 +
              {PERIODICITY_OPTIONS.map((opt) => (
129 +
                <TouchableOpacity
130 +
                  key={opt.value}
131 +
                  style={[
132 +
                    styles.periodicityChip,
133 +
                    periodicity === opt.value && styles.periodicityChipActive,
134 +
                  ]}
135 +
                  onPress={() => {
136 +
                    setPeriodicity(opt.value);
137 +
                    const count = getTimePickerCount(opt.value);
138 +
                    setScheduledTimes((prev) =>
139 +
                      count === 0
140 +
                        ? []
141 +
                        : count > prev.length
142 +
                        ? [...prev, ...Array(count - prev.length).fill(new Date())]
143 +
                        : prev.slice(0, count)
144 +
                    );
145 +
                  }}
146 +
                  activeOpacity={0.7}
147 +
                >
148 +
                  <Text
149 +
                    style={[
150 +
                      styles.periodicityChipLabel,
151 +
                      periodicity === opt.value && styles.periodicityChipLabelActive,
152 +
                    ]}
153 +
                  >
154 +
                    {opt.label}
155 +
                  </Text>
156 +
                </TouchableOpacity>
157 +
              ))}
158 +
            </View>
159 +
          </Section>
160 +

161 +
          {scheduledTimes.length > 0 && (
162 +
            <Section>
163 +
              <SectionHeader
164 +
                title={scheduledTimes.length === 1 ? "Horário" : "Horários"}
165 +
              />
166 +
              {scheduledTimes.map((t, i) => (
167 +
                <View style={{ marginTop: 10 }} key={i}>
168 +
                  <TimePicker
169 +
                    value={t}
170 +
                    onChange={(date) =>
171 +
                      setScheduledTimes((prev) =>
172 +
                        prev.map((v, idx) => (idx === i ? date : v))
173 +
                      )
174 +
                    }
175 +
                  />
176 +
                </View>
177 +
              ))}
178 +
            </Section>
179 +
          )}
180 +

181 +
          <Button
182 +
            title="Salvar"
183 +
            onPress={handleSave}
184 +
            loading={updateRegimen.isPending}
185 +
            disabled={!name || !dosage}
186 +
          />
187 +
        </View>
188 +
      </ScrollView>
189 +
    </KeyboardAvoidingView>
13 190
  );
14 191
}
15 192

16 193
const styles = StyleSheet.create({
194 +
  keyboardView: { flex: 1 },
195 +
  center: { flex: 1, alignItems: "center", justifyContent: "center" },
17 196
  container: {
18 -
    flex: 1,
19 -
    padding: Spacing.containerPadding,
20 -
    alignItems: "center",
21 -
    justifyContent: "center",
197 +
    gap: 24,
198 +
    paddingHorizontal: Spacing.containerPadding,
199 +
    paddingVertical: Spacing.sectionGap,
200 +
    marginBottom: 50,
201 +
  },
202 +
  header: { gap: 8 },
203 +
  headerTitle: { ...Typography.headlineMd },
204 +
  headerSubtitle: {
205 +
    ...Typography.bodyMd,
206 +
    color: Colors.light.textSecondary,
207 +
  },
208 +
  periodicityRow: {
209 +
    flexDirection: "row",
210 +
    flexWrap: "wrap",
211 +
    gap: 8,
212 +
  },
213 +
  periodicityChip: {
214 +
    paddingHorizontal: 16,
215 +
    paddingVertical: 10,
216 +
    borderRadius: BorderRadius.full,
217 +
    backgroundColor: "#fff",
218 +
    borderWidth: 1.5,
219 +
    borderColor: Colors.light.divider,
220 +
  },
221 +
  periodicityChipActive: {
222 +
    backgroundColor: Colors.light.tint + "1A",
223 +
    borderColor: Colors.light.tint,
224 +
  },
225 +
  periodicityChipLabel: {
226 +
    fontSize: 14,
227 +
    fontWeight: "500",
228 +
    color: Colors.light.text,
22 229
  },
23 -
  title: { ...Typography.headlineMd },
24 -
  subtitle: { ...Typography.bodyMd, color: Colors.light.textSecondary, marginTop: 8 },
230 +
  periodicityChipLabelActive: {
231 +
    fontWeight: "600",
232 +
    color: Colors.light.text,
233 +
  },
25 234
});