eletrotupi / tcc/ commit / e32e5ac

frontend: introduce an edit sleep record & triggers page

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago e32e5ac879d9fad0a4a3640e150701d9eaea7378
Parents: a48e311
2 file(s) changed
  • frontend/app/sleep/edit/[id].tsx +176 -0
  • frontend/app/triggers/edit/[id].tsx +179 -0
frontend/app/sleep/edit/[id].tsx
@@ -0,0 +1,176 @@
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 + });
frontend/app/triggers/edit/[id].tsx
@@ -0,0 +1,179 @@
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 + });