eletrotupi / tcc/ commit / e32e5ac

frontend: introduce an edit sleep record & triggers page

Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago e32e5ac879d9fad0a4a3640e150701d9eaea7378 Browse files
Parents a48e311
Changed 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 +
});