eletrotupi / tcc/ frontend/app/(tabs)/new/post-mood.tsxafa605370e2af884a10edffbb025e6ee263ed462
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, 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}>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
      />
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
});