eletrotupi / tcc/ frontend/app/(tabs)/new/post-mood.tsx1177778ae361b58371a1dabb3aeec3b0b03e0ff9
10.8 KB | 375 lines Raw
1
import { useState } from "react";
2
import {
3
  View,
4
  Text,
5
  StyleSheet,
6
  TouchableOpacity,
7
  Pressable,
8
  ActivityIndicator,
9
} from "react-native";
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
    setLinked(true);
45
    setSelectedTrigger(null);
46
  };
47

48
  const onNotificationPress = () => {
49
    router.push("/notifications");
50
  };
51

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

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

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

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

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

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

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

202
      <Button
203
        title="Pular essa etapa"
204
        variant="ghost"
205
        onPress={() => router.replace("/(tabs)/new")}
206
        style={styles.concludeButton}
207
      />
208
    </ScreenLayout>
209
  );
210
}
211

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