frontend: rework the edit medicine screen
Parents:
ca6234d1 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
});