eletrotupi / tcc/ commit / 6b55ce5

frontend: flesh mood components screen

Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago 6b55ce5b08c2cf596a6f02f8e37e459809efd41d Browse files
Parents 2b711ef
Changed files
1
Additions
+245
Deletions
0
/dev/null → frontend/app/entry/mood-components.tsx
1 +
import React from 'react';
2 +
import {
3 +
  View,
4 +
  Text,
5 +
  ScrollView,
6 +
  Pressable,
7 +
  StyleSheet,
8 +
} from 'react-native';
9 +
import { router } from 'expo-router';
10 +
import { Ionicons } from '@expo/vector-icons';
11 +

12 +
import { ScaleSlider } from '@/components/ui/ScaleSlider';
13 +
import { Row, Grid } from '@/components/ui/LayoutHelpers';
14 +
import { Section, SectionHeader } from '@/components/ui/Sections';
15 +
import { Button } from '@/components/ui/Button';
16 +
import { Card } from '@/components/ui/Cards';
17 +
import { Theme } from '@/constants/theme';
18 +
import {
19 +
  MOOD_COMPONENTS,
20 +
  getMoodComponent,
21 +
  intensityLabel,
22 +
} from '@/constants/mood-components';
23 +

24 +
import {
25 +
  useMoodEntryStore,
26 +
} from '@/stores/moodEntry';
27 +

28 +
export default function MoodComponentsScreen() {
29 +
  const {
30 +
    selectedMood,
31 +
    components,
32 +
    addComponent,
33 +
    removeComponent,
34 +
    setComponentIntensity,
35 +
  } = useMoodEntryStore();
36 +

37 +
  const activeIds = components.map((c) => c.id);
38 +
  const available = MOOD_COMPONENTS.filter((d) => !activeIds.includes(d.id));
39 +

40 +
  const handleDone = () => {
41 +
    router.back();
42 +
  };
43 +

44 +
  return (
45 +
    <View style={styles.container}>
46 +

47 +
      {/* Header */}
48 +
      <View style={styles.header}>
49 +
        <Pressable onPress={() => router.back()} hitSlop={8}>
50 +
          <Ionicons name="close" size={24} color={Theme.colors.text} />
51 +
        </Pressable>
52 +
        <Text style={styles.headerTitle}>Editar Componentes</Text>
53 +
        <Pressable onPress={handleDone} hitSlop={8}>
54 +
          <Text style={styles.headerAction}>Pronto</Text>
55 +
        </Pressable>
56 +
      </View>
57 +

58 +
      <ScrollView
59 +
        contentContainerStyle={styles.content}
60 +
        showsVerticalScrollIndicator={false}
61 +
      >
62 +

63 +
        {/* Subtitle */}
64 +
        {selectedMood && (
65 +
          <View style={styles.subtitleBlock}>
66 +
            <Text style={styles.subtitleSmall}>Baseado no seu humor selecionado</Text>
67 +
            <Text style={styles.subtitleLarge}>{selectedMood.label}</Text>
68 +
          </View>
69 +
        )}
70 +

71 +
        {/* Active components */}
72 +
        {components.length > 0 && (
73 +
          <Section>
74 +
            <SectionHeader
75 +
              title="Intensidade dos Componentes"
76 +
              info={`Ativos: ${components.length}`} />
77 +

78 +
            {components.map((comp) => {
79 +
              const def = getMoodComponent(comp.id);
80 +
              if (!def) return null;
81 +

82 +
              return (
83 +
                <Card key={comp.id} style={styles.activeCard}>
84 +
                  {/* Card header */}
85 +
                  <Row style={styles.activeCardHeader}>
86 +
                    <View style={[styles.dot, { backgroundColor: def.color }]} />
87 +
                    <Text style={styles.activeCardLabel}>{def.label}</Text>
88 +
                    <Pressable
89 +
                      onPress={() => removeComponent(comp.id)}
90 +
                      hitSlop={8}
91 +
                    >
92 +
                      <Ionicons
93 +
                        name="remove-circle-outline"
94 +
                        size={22}
95 +
                        color="#F87171"
96 +
                      />
97 +
                    </Pressable>
98 +
                  </Row>
99 +

100 +
                  {/* Slider */}
101 +
                  <ScaleSlider
102 +
                    variant="compact"
103 +
                    label="Intensidade"
104 +
                    value={comp.intensity}
105 +
                    onValueChange={(v) => setComponentIntensity(comp.id, v)}
106 +
                    min={1}
107 +
                    max={10}
108 +
                    step={1}
109 +
                    minLabel="Suave"
110 +
                    maxLabel="Intensa"
111 +
                    style={styles.slider}
112 +
                  />
113 +
                </Card>
114 +
              );
115 +
            })}
116 +

117 +
          </Section>
118 +
        )}
119 +

120 +
        {/* Available to add */}
121 +
        {available.length > 0 && (
122 +
          <Section>
123 +
            <SectionHeader title="Adicionar outros" />
124 +
            <Grid gap={2}>
125 +
              {available.map((def) => (
126 +
                <Card key={def.id} style={styles.availableItem}>
127 +
                  <Pressable
128 +
                    onPress={() => addComponent(def.id)}
129 +
                    style={styles.availableItemInternalWrapper}
130 +
                  >
131 +
                    <Row gap={8} style={styles.availableInner}>
132 +
                      <View style={[styles.dot, { backgroundColor: def.color }]} />
133 +
                      <Text style={styles.availableLabel}>{def.label}</Text>
134 +
                    </Row>
135 +

136 +
                    <Ionicons
137 +
                      name="add-circle-outline"
138 +
                      size={20}
139 +
                      color={Theme.colors.light.smallAddButtonIcon}
140 +
                    />
141 +
                  </Pressable>
142 +
                </Card>
143 +
              ))}
144 +
            </Grid>
145 +
          </Section>
146 +
        )}
147 +
      </ScrollView>
148 +
    </View>
149 +
  );
150 +
}
151 +

152 +
const styles = StyleSheet.create({
153 +
  container: {
154 +
    flex: 1,
155 +
  },
156 +
  header: {
157 +
    flexDirection: 'row',
158 +
    justifyContent: 'space-between',
159 +
    alignItems: 'center',
160 +
    paddingHorizontal: Theme.spacing.containerPadding,
161 +
    paddingVertical: 16,
162 +
    borderBottomWidth: 1,
163 +
    borderBottomColor: Theme.colors.light.divider,
164 +
  },
165 +
  headerTitle: {
166 +
    fontSize: Theme.typography.bodyLg.fontSize,
167 +
    fontWeight: '700',
168 +
    color: Theme.colors.light.text,
169 +
  },
170 +
  headerAction: {
171 +
    fontSize: Theme.typography.bodyMd.fontSize,
172 +
    fontWeight: '600',
173 +
    color: Theme.colors.light.tint,
174 +
  },
175 +
  content: {
176 +
    padding: Theme.spacing.containerPadding,
177 +
    gap: Theme.spacing.sectionGap,
178 +
    paddingBottom: 120,
179 +
  },
180 +
  subtitleBlock: {
181 +
    alignItems: 'center',
182 +
    gap: 4,
183 +
  },
184 +
  subtitleSmall: {
185 +
    fontSize: Theme.typography.bodyMd.fontSize,
186 +
    color: Theme.colors.light.textSecondary,
187 +
  },
188 +
  subtitleLarge: {
189 +
    fontSize: 28,
190 +
    fontWeight: '700',
191 +
    color: Theme.colors.light.text,
192 +
  },
193 +
  section: {
194 +
    gap: Theme.spacing.cardGap,
195 +
  },
196 +
  sectionHeader: {
197 +
    justifyContent: 'space-between',
198 +
    alignItems: 'center',
199 +
  },
200 +
  activeCard: {
201 +
    padding: Theme.spacing.cardGap,
202 +
  },
203 +
  activeCardHeader: {
204 +
    alignItems: 'center',
205 +
    justifyContent: 'space-between',
206 +
    marginBottom: 16
207 +
  },
208 +
  activeCardLabel: {
209 +
    flex: 1,
210 +
    ... { ...Theme.typography.bodyLg, lineHeight: 24 },
211 +
    color: Theme.colors.light.text,
212 +
    marginLeft: 4,
213 +
  },
214 +
  dot: {
215 +
    width: 12,
216 +
    height: 12,
217 +
    borderRadius: 6,
218 +
    flexShrink: 0,
219 +
  },
220 +
  slider: {
221 +
    marginTop: 4,
222 +
  },
223 +
  availableItem: {
224 +
    flexDirection: 'row',
225 +
    justifyContent: 'space-between',
226 +
    alignItems: 'center',
227 +
    paddingHorizontal: 12,
228 +
    paddingVertical: 10,
229 +
  },
230 +
  availableItemInternalWrapper: {
231 +
    flexDirection: 'row',
232 +
    width: '100%',
233 +
    justifyContent: 'space-between',
234 +
    alignItems: 'center',
235 +
  },
236 +
  availableInner: {
237 +
    alignItems: 'center',
238 +
    flexShrink: 1,
239 +
  },
240 +
  availableLabel: {
241 +
    fontSize: Theme.typography.bodyMd.fontSize,
242 +
    fontWeight: '500',
243 +
    color: Theme.colors.light.text,
244 +
  }
245 +
});