frontend: introduce an edit sleep record & triggers page
Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago
e32e5ac879d9fad0a4a3640e150701d9eaea7378 Browse filesParents
a48e311Changed files
2
Additions
+355
Deletions
0
/dev/null → frontend/app/sleep/edit/[id].tsx
1 +
import {
2 +
View,
3 +
Text,
4 +
StyleSheet,
5 +
KeyboardAvoidingView,
6 +
ScrollView,
7 +
} from "react-native";
8 +
import { useState, useEffect } from "react";
9 +
import { router, useLocalSearchParams, Stack } from "expo-router";
10 +
11 +
import { Card } from "@/components/ui/Cards";
12 +
import { TextArea } from "@/components/ui/Input";
13 +
import { Button } from "@/components/ui/Button";
14 +
import { Spacing, Typography, Colors, BorderRadius } from "@/constants/theme";
15 +
import { Ionicons } from "@expo/vector-icons";
16 +
import { DatePickerField } from "@/components/ui/DatePickerField";
17 +
import { ScaleSlider } from "@/components/ui/ScaleSlider";
18 +
import { useSleepRecord, useUpdateSleepRecord } from "@/hooks";
19 +
import { parseDateOnly } from "@/lib/utils/time";
20 +
import { ActivityIndicator, View as ActivityView } from "react-native";
21 +
22 +
export default function EditSleepRecord() {
23 +
const { id } = useLocalSearchParams<{ id: string }>();
24 +
const { data: sleepRecord, isLoading } = useSleepRecord(id);
25 +
const updateSleepRecord = useUpdateSleepRecord();
26 +
27 +
const [date, setDate] = useState(new Date());
28 +
const [annotations, setAnnotations] = useState("");
29 +
const [average, setAverage] = useState(7.5);
30 +
31 +
useEffect(() => {
32 +
if (sleepRecord) {
33 +
setDate(parseDateOnly(sleepRecord.date));
34 +
setAnnotations(sleepRecord.annotations || "");
35 +
setAverage(sleepRecord.average);
36 +
}
37 +
}, [sleepRecord]);
38 +
39 +
const formatValue = (value: number): string => {
40 +
return String(value).replaceAll(".", ",");
41 +
};
42 +
43 +
const save = async () => {
44 +
const data = {
45 +
date,
46 +
annotations,
47 +
average,
48 +
};
49 +
50 +
await updateSleepRecord.mutateAsync({ id: String(id), payload: data });
51 +
router.back();
52 +
};
53 +
54 +
if (isLoading) {
55 +
return (
56 +
<ActivityView style={styles.centered}>
57 +
<ActivityIndicator size="large" color={Colors.light.tint} />
58 +
</ActivityView>
59 +
);
60 +
}
61 +
62 +
return (
63 +
<KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
64 +
<Stack.Screen options={{ title: "Editar Registro" }} />
65 +
<ScrollView scrollEventThrottle={16}>
66 +
<View style={styles.container}>
67 +
<View style={styles.header}>
68 +
<View style={styles.innerHeaderWrapper}>
69 +
<View style={[styles.headerIcon, { backgroundColor: "#EFF6FF" }]}>
70 +
<Ionicons name="moon-outline" size={20} color="#2563EB" />
71 +
</View>
72 +
73 +
<View style={styles.headerTitleSubtitle}>
74 +
<Text style={styles.headerTitle}>Editar Sono</Text>
75 +
<Text style={styles.headerSubtitle}>
76 +
Atualize os dados do sono
77 +
</Text>
78 +
</View>
79 +
</View>
80 +
81 +
<Text style={styles.description}>
82 +
Você pode editar este registro nos primeiros 5 minutos após a
83 +
criação.
84 +
</Text>
85 +
</View>
86 +
87 +
<Card style={styles.mainCard}>
88 +
<DatePickerField
89 +
label="Data do Registro"
90 +
initialDate={date}
91 +
maximumDate={new Date()}
92 +
onChange={setDate}
93 +
/>
94 +
95 +
<View style={{ marginVertical: 15 }}>
96 +
<ScaleSlider
97 +
label="Duração"
98 +
value={average}
99 +
onValueChange={setAverage}
100 +
onValueFormat={formatValue}
101 +
step={0.5}
102 +
min={0.0}
103 +
max={15.0}
104 +
minLabel="0h"
105 +
maxLabel="15h"
106 +
/>
107 +
</View>
108 +
109 +
<TextArea
110 +
label="Notas sobre a qualidade"
111 +
variant="darkGhost"
112 +
onChangeText={(val) => setAnnotations(val)}
113 +
value={annotations}
114 +
minRows={5}
115 +
maxRows={10}
116 +
placeholder="Como você se sentiu ao acordar? Teve sonhos marcantes?"
117 +
/>
118 +
</Card>
119 +
120 +
<Button
121 +
title="Salvar Alterações"
122 +
onPress={save}
123 +
isLoading={updateSleepRecord.isPending}
124 +
disabled={updateSleepRecord.isPending}
125 +
/>
126 +
</View>
127 +
</ScrollView>
128 +
</KeyboardAvoidingView>
129 +
);
130 +
}
131 +
132 +
const styles = StyleSheet.create({
133 +
container: {
134 +
gap: 24,
135 +
paddingHorizontal: Spacing.containerPadding,
136 +
paddingVertical: Spacing.sectionGap,
137 +
},
138 +
mainCard: {
139 +
padding: Spacing.cardGap,
140 +
},
141 +
header: {},
142 +
headerIcon: {
143 +
paddingVertical: 15,
144 +
paddingHorizontal: 14,
145 +
width: 48,
146 +
borderRadius: BorderRadius.md,
147 +
},
148 +
innerHeaderWrapper: {
149 +
flex: 1,
150 +
flexDirection: "row",
151 +
alignItems: "center",
152 +
},
153 +
headerTitleSubtitle: {
154 +
marginLeft: 10,
155 +
},
156 +
headerTitle: {
157 +
...Typography.headlineMd,
158 +
},
159 +
headerSubtitle: {
160 +
...Typography.bodyMd,
161 +
color: Colors.light.textSecondary,
162 +
},
163 +
description: {
164 +
...Typography.bodyMd,
165 +
color: Colors.light.textSecondary,
166 +
marginTop: 20,
167 +
},
168 +
keyboardView: {
169 +
flex: 1,
170 +
},
171 +
centered: {
172 +
flex: 1,
173 +
justifyContent: "center",
174 +
alignItems: "center",
175 +
},
176 +
});
/dev/null → frontend/app/triggers/edit/[id].tsx
1 +
import {
2 +
View,
3 +
Text,
4 +
StyleSheet,
5 +
KeyboardAvoidingView,
6 +
ScrollView,
7 +
ActivityIndicator,
8 +
} from "react-native";
9 +
import { useState, useEffect } from "react";
10 +
import { router, useLocalSearchParams, Stack } from "expo-router";
11 +
12 +
import { Card } from "@/components/ui/Cards";
13 +
import { TextArea } 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 { Ionicons, MaterialIcons } from "@expo/vector-icons";
18 +
import { useUpdateTrigger, useTrigger } from "@/hooks";
19 +
import { CategoryChips, CategoryOption } from "@/components/ui/CategoryChips";
20 +
import { TriggerCategory } from "@/constants/triggers";
21 +
import { TRIGGER_CATEGORY_DEFINITIONS } from "@/constants/trigger-categories";
22 +
import { TimePicker } from "@/components/ui/TimePicker";
23 +
24 +
const TRIGGER_CATEGORIES: CategoryOption<TriggerCategory>[] =
25 +
TRIGGER_CATEGORY_DEFINITIONS.map((d) => ({
26 +
label: d.label,
27 +
value: d.id,
28 +
icon: <MaterialIcons size={16} color={d.color} name={d.icon} />,
29 +
}));
30 +
31 +
export default function EditTrigger() {
32 +
const { id } = useLocalSearchParams<{ id: string }>();
33 +
const { data: trigger, isLoading } = useTrigger(id);
34 +
const updateTrigger = useUpdateTrigger();
35 +
36 +
const [moment, setMoment] = useState(new Date());
37 +
const [comment, setComment] = useState("");
38 +
const [category, setCategory] = useState("WORK");
39 +
40 +
useEffect(() => {
41 +
if (trigger) {
42 +
setMoment(new Date(trigger.moment));
43 +
setComment(trigger.comment || "");
44 +
setCategory(trigger.category);
45 +
}
46 +
}, [trigger]);
47 +
48 +
const save = async () => {
49 +
const data = {
50 +
moment,
51 +
comment,
52 +
category,
53 +
};
54 +
55 +
await updateTrigger.mutateAsync({ id: String(id), payload: data });
56 +
router.back();
57 +
};
58 +
59 +
if (isLoading) {
60 +
return (
61 +
<View style={styles.centered}>
62 +
<ActivityIndicator size="large" color={Colors.light.tint} />
63 +
</View>
64 +
);
65 +
}
66 +
67 +
return (
68 +
<KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
69 +
<Stack.Screen options={{ title: "Editar Registro" }} />
70 +
<ScrollView scrollEventThrottle={16}>
71 +
<View style={styles.container}>
72 +
<View style={styles.header}>
73 +
<View style={styles.innerHeaderWrapper}>
74 +
<View style={[styles.headerIcon, { backgroundColor: "#FFF7ED" }]}>
75 +
<Ionicons name="flash-outline" size={20} color="#EA580C" />
76 +
</View>
77 +
78 +
<View style={styles.headerTitleSubtitle}>
79 +
<Text style={styles.headerTitle}>Editar Gatilho</Text>
80 +
<Text style={styles.headerSubtitle}>
81 +
Atualize os dados do acontecimento
82 +
</Text>
83 +
</View>
84 +
</View>
85 +
86 +
<Text style={styles.description}>
87 +
Você pode editar este registro nos primeiros 5 minutos após a
88 +
criação.
89 +
</Text>
90 +
</View>
91 +
92 +
<Section>
93 +
<SectionHeader title="Horário do Evento" />
94 +
<TimePicker value={moment} onChange={setMoment} />
95 +
</Section>
96 +
97 +
<Section>
98 +
<SectionHeader title="Selecionar uma categoria" />
99 +
<CategoryChips
100 +
options={TRIGGER_CATEGORIES}
101 +
active={category}
102 +
onChange={setCategory}
103 +
/>
104 +
</Section>
105 +
106 +
<Section>
107 +
<SectionHeader title="Adicione detalhes" />
108 +
109 +
<Card style={styles.mainCard}>
110 +
<TextArea
111 +
label="Comentários sobre"
112 +
variant="darkGhost"
113 +
onChangeText={(val) => setComment(val)}
114 +
value={comment}
115 +
minRows={5}
116 +
maxRows={10}
117 +
placeholder="Descreva o que gerou esse sentimento"
118 +
/>
119 +
</Card>
120 +
</Section>
121 +
122 +
<Button
123 +
title="Salvar Alterações"
124 +
onPress={save}
125 +
isLoading={updateTrigger.isPending}
126 +
disabled={updateTrigger.isPending}
127 +
/>
128 +
</View>
129 +
</ScrollView>
130 +
</KeyboardAvoidingView>
131 +
);
132 +
}
133 +
134 +
const styles = StyleSheet.create({
135 +
container: {
136 +
gap: 24,
137 +
paddingHorizontal: Spacing.containerPadding,
138 +
paddingVertical: Spacing.sectionGap,
139 +
marginBottom: 50,
140 +
},
141 +
mainCard: {
142 +
padding: Spacing.cardGap,
143 +
},
144 +
header: {},
145 +
headerIcon: {
146 +
paddingVertical: 15,
147 +
paddingHorizontal: 14,
148 +
width: 48,
149 +
borderRadius: BorderRadius.md,
150 +
},
151 +
innerHeaderWrapper: {
152 +
flex: 1,
153 +
flexDirection: "row",
154 +
alignItems: "center",
155 +
},
156 +
headerTitleSubtitle: {
157 +
marginLeft: 10,
158 +
},
159 +
headerTitle: {
160 +
...Typography.headlineMd,
161 +
},
162 +
headerSubtitle: {
163 +
...Typography.bodyMd,
164 +
color: Colors.light.textSecondary,
165 +
},
166 +
description: {
167 +
...Typography.bodyMd,
168 +
color: Colors.light.textSecondary,
169 +
marginTop: 20,
170 +
},
171 +
keyboardView: {
172 +
flex: 1,
173 +
},
174 +
centered: {
175 +
flex: 1,
176 +
justifyContent: "center",
177 +
alignItems: "center",
178 +
},
179 +
});