eletrotupi / tcc/ frontend/app/(tabs)/new/post-mood.tsx6b0e21c082d83d67530667a986f1cd80d982458e
10.8 KB | 374 lines Raw
1
import { useState } from "react";
2
import {
3
  View,
4
  StyleSheet,
5
  TouchableOpacity,
6
  Pressable,
7
  ActivityIndicator,
8
} from "react-native";
9
import { Text } from '@/components/ui/Text';
10
import { router, useLocalSearchParams, Link } from "expo-router";
11
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
12

13
import { ScreenLayout } from "@/components/ui/ScreenLayout";
14
import { Card } from "@/components/ui/Cards";
15
import { Section } from '@/components/ui/Sections';
16
import { Button } from "@/components/ui/Button";
17
import { useAuth } from "@/context/AuthContext";
18
import { Colors, Spacing, Typography, BorderRadius, Shadows } from "@/constants/theme";
19
import { getTriggerCategory } from "@/constants/trigger-categories";
20
import { useTriggers, useLinkMoodToTrigger } from "@/hooks";
21
import { Trigger } from "@/lib/api";
22
import { formatMoment } from '@/lib/utils/time';
23

24
export default function PostMood() {
25
  const { moodId } = useLocalSearchParams<{ moodId: string }>();
26
  const { user } = useAuth();
27

28
  const [selectedTrigger, setSelectedTrigger] = useState<Trigger | null>(null);
29
  const [impact, setImpact] = useState(3);
30
  const [linked, setLinked] = useState(false);
31

32
  const { data: triggersData, isLoading: isLoadingTriggers } = useTriggers({ limit: 3 });
33
  const recentTriggers = triggersData?.entries ?? [];
34

35
  const linkMoodToTrigger = useLinkMoodToTrigger();
36

37
  const handleLink = async () => {
38
    if (!selectedTrigger || !moodId) return;
39
    await linkMoodToTrigger.mutateAsync({
40
      triggerId: String(selectedTrigger.id),
41
      moodId,
42
      perceivedImpact: impact,
43
    });
44

45
    setLinked(true);
46
    setSelectedTrigger(null);
47
  };
48

49
  return (
50
    <ScreenLayout
51
      userName={user?.firstName}
52
      userAvatar={user?.avatarURL}
53
    >
54
      {/* Confirmation block */}
55
      <View style={styles.confirmationBlock}>
56
        <View style={styles.outerRing}>
57
          <View style={styles.innerCircle}>
58
            <Ionicons name="checkmark" size={32} color="#fff" />
59
          </View>
60
        </View>
61
        <Text style={styles.confirmTitle} testID="text-mood-saved">Humor Salvo!</Text>
62
        <Text style={styles.confirmSubtitle}>
63
          Seu registro foi armazenado com sucesso.
64
        </Text>
65
      </View>
66

67
      {/* Link prompt card */}
68
      {linked ? (
69
        <View style={styles.linkedRow}>
70
          <Ionicons name="checkmark-circle" size={20} color={Colors.light.tint} />
71
          <Text style={styles.linkedText}>Gatilho vinculado</Text>
72
        </View>
73
      ) : (
74
        <Card style={styles.linkCard}>
75
          {selectedTrigger ? (
76
            /* Impact picker state */
77
            <View style={styles.impactPicker}>
78
              {/* Selected trigger header */}
79
              <View style={styles.selectedTriggerRow}>
80
                {(() => {
81
                  const cat = getTriggerCategory(selectedTrigger.category);
82
                  return (
83
                    <>
84
                      <View style={[styles.triggerIconBubble, { backgroundColor: cat.iconBackground }]}>
85
                        <MaterialIcons name={cat.icon} size={18} color={cat.color} />
86
                      </View>
87
                      <Text style={styles.selectedTriggerLabel} numberOfLines={1}>
88
                        {cat.label}
89
                      </Text>
90
                    </>
91
                  );
92
                })()}
93
                <TouchableOpacity
94
                  onPress={() => setSelectedTrigger(null)}
95
                  hitSlop={8}
96
                  style={styles.deselectButton}
97
                >
98
                  <Ionicons name="close" size={18} color={Colors.light.textSecondary} />
99
                </TouchableOpacity>
100
              </View>
101

102
              <Text style={styles.impactLabel}>Qual foi o impacto?</Text>
103

104
              <View style={styles.impactButtons}>
105
                {[1, 2, 3, 4, 5].map((n) => {
106
                  const isSelected = impact === n;
107
                  return (
108
                    <TouchableOpacity
109
                      key={n}
110
                      style={[
111
                        styles.impactCircle,
112
                        isSelected && styles.impactCircleSelected,
113
                      ]}
114
                      onPress={() => setImpact(n)}
115
                      activeOpacity={0.7}
116
                    >
117
                      <Text
118
                        style={[
119
                          styles.impactCircleText,
120
                          isSelected && styles.impactCircleTextSelected,
121
                        ]}
122
                      >
123
                        {n}
124
                      </Text>
125
                    </TouchableOpacity>
126
                  );
127
                })}
128
              </View>
129

130
              <Button
131
                title="Vincular"
132
                onPress={handleLink}
133
                loading={linkMoodToTrigger.isPending}
134
                style={{ marginTop: 4, ...Shadows.lg }}
135
              />
136
            </View>
137
          ) : (
138
            /* Trigger selection state */
139
            <>
140
              <Text style={styles.linkCardTitle}>
141
                Algo aconteceu que pode ter influenciado esse humor?
142
              </Text>
143
              <Text style={styles.linkCardSubtitle}>
144
                Selecione um gatilho recente ou adicione um novo.
145
              </Text>
146

147
              {isLoadingTriggers ? (
148
                <ActivityIndicator style={{ marginTop: 12 }} />
149
              ) : (
150
                <View style={styles.triggerGrid}>
151
                  {recentTriggers.map((trigger) => {
152
                    const cat = getTriggerCategory(trigger.category);
153
                    return (
154
                      <Pressable
155
                        key={trigger.id}
156
                        style={({ pressed }) => [
157
                          styles.triggerGridItem,
158
                          pressed && { opacity: 0.7 },
159
                        ]}
160
                        onPress={() => setSelectedTrigger(trigger)}
161
                      >
162
                        <View style={[styles.triggerIconBubble, { backgroundColor: cat.iconBackground }]}>
163
                          <MaterialIcons name={cat.icon} size={20} color={cat.color} />
164
                        </View>
165
                        <Text style={styles.triggerItemLabel} numberOfLines={2}>
166
                          {cat.label}
167
                        </Text>
168
                        <Text style={styles.triggerItemLabel} numberOfLines={2}>
169
                          {formatMoment(trigger.moment)}
170
                        </Text>
171
                      </Pressable>
172
                    );
173
                  })}
174

175
                  {/* New trigger item */}
176
                  <Pressable
177
                    style={({ pressed }) => [
178
                      styles.triggerGridItem,
179
                      styles.newTriggerItem,
180
                      pressed && { opacity: 0.7 },
181
                    ]}
182
                    onPress={() => router.push(`/triggers/new?moodId=${moodId}`)}
183
                  >
184
                    <Ionicons
185
                      name="add-circle-outline"
186
                      size={24}
187
                      color={Colors.light.textSecondary}
188
                    />
189
                    <Text style={styles.newTriggerLabel}>Novo gatilho</Text>
190
                  </Pressable>
191
                </View>
192
              )}
193
            </>
194
          )}
195
        </Card>
196
      )}
197

198
      <Button
199
        title={linked ? 'Voltar para a tela inicial' : 'Pular essa etapa'}
200
        variant="ghost"
201
        onPress={() => router.replace("/(tabs)/new")}
202
        style={styles.concludeButton}
203
        testID="button-conclude"
204
      />
205
    </ScreenLayout>
206
  );
207
}
208

209
const styles = StyleSheet.create({
210
  confirmationBlock: {
211
    alignItems: "center",
212
    marginBottom: Spacing.sectionGap,
213
    gap: 12,
214
  },
215
  outerRing: {
216
    width: 88,
217
    height: 88,
218
    borderRadius: 44,
219
    backgroundColor: "#E7FDEF",
220
    alignItems: "center",
221
    justifyContent: "center",
222
  },
223
  innerCircle: {
224
    width: 64,
225
    height: 64,
226
    borderRadius: 32,
227
    backgroundColor: Colors.light.tint,
228
    alignItems: "center",
229
    justifyContent: "center",
230
  },
231
  confirmTitle: {
232
    ...Typography.headlineMd,
233
    fontWeight: "700",
234
    color: Colors.light.text,
235
    textAlign: "center",
236
  },
237
  confirmSubtitle: {
238
    ...Typography.bodyMd,
239
    color: Colors.light.textSecondary,
240
    textAlign: "center",
241
  },
242
  linkCard: {
243
    padding: Spacing.cardGap,
244
    marginBottom: Spacing.sectionGap,
245
  },
246
  linkCardTitle: {
247
    ...Typography.bodyMd,
248
    fontWeight: "700",
249
    color: Colors.light.text,
250
    marginBottom: 4,
251
  },
252
  linkCardSubtitle: {
253
    ...Typography.bodyMd,
254
    color: Colors.light.textSecondary,
255
    marginBottom: 16,
256
  },
257
  triggerGrid: {
258
    flexDirection: "row",
259
    flexWrap: "wrap",
260
    gap: 10,
261
  },
262
  triggerGridItem: {
263
    width: "48%",
264
    minHeight: 80,
265
    backgroundColor: "#fff",
266
    borderRadius: BorderRadius.md,
267
    borderWidth: 1,
268
    borderColor: Colors.light.divider,
269
    alignItems: "center",
270
    justifyContent: "center",
271
    padding: 10,
272
    gap: 6,
273
    ...Shadows.sm,
274
  },
275
  newTriggerItem: {
276
    borderStyle: "dashed",
277
    borderColor: Colors.light.borderDashed,
278
    backgroundColor: "transparent",
279
    shadowColor: "#000000",
280
    shadowOffset: { width: 0, height: 0 },
281
    shadowOpacity: 0,
282
    shadowRadius: 0,
283
    elevation: 0,
284
  },
285
  triggerIconBubble: {
286
    width: 36,
287
    height: 36,
288
    borderRadius: 18,
289
    alignItems: "center",
290
    justifyContent: "center",
291
  },
292
  triggerItemLabel: {
293
    ...Typography.labelSm,
294
    color: Colors.light.text,
295
    textAlign: "center",
296
  },
297
  newTriggerLabel: {
298
    ...Typography.labelSm,
299
    color: Colors.light.textSecondary,
300
    textAlign: "center",
301
  },
302
  // Impact picker
303
  impactPicker: {
304
    gap: 12,
305
  },
306
  selectedTriggerRow: {
307
    flexDirection: "row",
308
    alignItems: "center",
309
    gap: 10,
310
    marginBottom: 4,
311
  },
312
  selectedTriggerLabel: {
313
    ...Typography.bodyMd,
314
    fontWeight: "600",
315
    color: Colors.light.text,
316
    flex: 1,
317
  },
318
  deselectButton: {
319
    padding: 4,
320
  },
321
  impactLabel: {
322
    ...Typography.bodyMd,
323
    fontWeight: "600",
324
    color: Colors.light.text,
325
  },
326
  impactButtons: {
327
    flexDirection: "row",
328
    gap: 10,
329
    justifyContent: "center",
330
  },
331
  impactCircle: {
332
    width: 48,
333
    height: 48,
334
    borderRadius: 24,
335
    backgroundColor: "#fff",
336
    borderWidth: 1.5,
337
    borderColor: Colors.light.divider,
338
    alignItems: "center",
339
    justifyContent: "center",
340
  },
341
  impactCircleSelected: {
342
    backgroundColor: Colors.light.tint + "1A",
343
    borderColor: Colors.light.tint,
344
  },
345
  impactCircleText: {
346
    fontSize: 16,
347
    fontWeight: "600",
348
    color: Colors.light.text,
349
  },
350
  impactCircleTextSelected: {
351
    color: Colors.light.text,
352
  },
353
  // Linked success
354
  linkedRow: {
355
    flexDirection: "row",
356
    alignItems: "center",
357
    justifyContent: "center",
358
    gap: 8,
359
    marginBottom: Spacing.sectionGap,
360
    paddingHorizontal: 4,
361
  },
362
  linkedText: {
363
    ...Typography.bodyMd,
364
    fontWeight: "600",
365
    color: Colors.light.tint,
366
    textAlign: 'center'
367
  },
368
  moodList: {
369
    gap: 8,
370
  },
371
  concludeButton: {
372
    marginTop: 8,
373
  },
374
});