eletrotupi / tcc/ frontend/app/(tabs)/new/post-mood.tsx729fa78803011f9075814181562b4f7ea4bb9974
10.5 KB | 364 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 } 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
    setLinked(true);
44
    setSelectedTrigger(null);
45
  };
46

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

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

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

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

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

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

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

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

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