eletrotupi / tcc/ frontend/app/(tabs)/new/post-mood.tsxea1475564abe0f26ea6bb9c6c840b714261aad91
10.8 KB | 373 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 } 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 { Button } from "@/components/ui/Button";
16
import { useAuth } from "@/context/AuthContext";
17
import { Colors, Spacing, Typography, BorderRadius, Shadows } from "@/constants/theme";
18
import { getTriggerCategory } from "@/constants/trigger-categories";
19
import { useTriggers, useLinkMoodToTrigger } from "@/hooks";
20
import { Trigger } from "@/lib/api";
21
import { formatMoment } from '@/lib/utils/time';
22

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

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

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

34
  const linkMoodToTrigger = useLinkMoodToTrigger();
35

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

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

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

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

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

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

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

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

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

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

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