eletrotupi / tcc/ commit / f1583df

frontend: introduce a better insights tab + some basic components

Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago f1583dfe90160d2a63fcf9773f9ef6ba70337d4e Browse files
Parents c456c24
Changed files
4
Additions
+321
Deletions
-34
frontend/app/(tabs)/insights.tsx
1 -
import { ThemedText } from '@/components/misc/themed-text';
2 -
import { ThemedView } from '@/components/misc/themed-view';
3 -
import { ScreenLayout } from '@/components/ui/ScreenLayout';
4 -
import { useAuth } from '@/context/AuthContext';
5 -
import { Card, CardHeader } from '@/components/ui/Cards'
6 -
import { Section, SectionHeader } from '@/components/ui/Sections'
7 -
import { useThemeColor } from '@/hooks/use-theme-color'
8 -
import { Spacing, BorderRadius } from '@/constants/theme'
9 -
import { Grid } from '@/components/ui/LayoutHelpers'
1 +
import { View, Text, ScrollView, StyleSheet } from "react-native";
2 +

3 +
import { ThemedText } from "@/components/misc/themed-text";
4 +
import { ThemedView } from "@/components/misc/themed-view";
5 +
import { ScreenLayout } from "@/components/ui/ScreenLayout";
6 +
import { useAuth } from "@/context/AuthContext";
7 +
import { Card } from "@/components/ui/Cards";
8 +
import { Section, SectionHeader } from "@/components/ui/Sections";
9 +
import { useThemeColor } from "@/hooks/use-theme-color";
10 +
import { Spacing, BorderRadius, Colors, Typography } from "@/constants/theme";
11 +
import { WeeklyResume } from "@/components/insights/WeeklyResume";
12 +
import { InsightCard, Highlight } from "@/components/insights/InsightCard";
13 +
import { MoodTrendWidget } from "@/components/insights/MoodTrendWidget";
10 14

11 15
export default function Insights() {
12 16
  const { user } = useAuth();
13 -
  const backgroundColor = useThemeColor({}, 'background')
14 -
  const accentPurple = useThemeColor({}, 'accentPurple')
15 17

16 18
  return (
17 -
    <ScreenLayout userName={user.firstName}
19 +
    <ScreenLayout
20 +
      userName={user.firstName}
18 21
      userAvatar={user.avatarURL}
19 -
      onNotificationPress={() => console.log('Notifications')}
20 -
      showNotificationBadge={true}>
21 -
      <ThemedView>
22 -
        <Section>
23 -
          <SectionHeader
24 -
            title="Tendências da Semana"
25 -
            info="Últimos 7 dias"
26 -
          />
27 -
          <Card variant="default" style={{ height: 200 }} />
28 -
        </Section>
22 +
      onNotificationPress={() => console.log("Notifications")}
23 +
      showNotificationBadge={true}
24 +
    >
25 +
      <WeeklyResume
26 +
        avgMood="Neutro"
27 +
        stats={[
28 +
          { label: "Média de Sono", value: "7.2h" },
29 +
          { label: "Humor Estável", value: "92%" },
30 +
        ]}
31 +
      />
32 +

33 +
      <Section>
34 +
        <SectionHeader title="Indicadores" />
35 +

36 +
        <InsightCard
37 +
          category="Energia"
38 +
          body={
39 +
            <Text style={styles.insightBody}>
40 +
              Aumento de <Highlight>20%</Highlight> correlacionado ao sono.
41 +
            </Text>
42 +
          }
43 +
          metric="+20%"
44 +
          accent="green"
45 +
        />
29 46

30 -
        <Section>
31 -
          <SectionHeader title="Insights de Saúde" />
32 -
          <Grid gap={8}>
33 -
            <Card style={{ height: 150, backgroundColor: accentPurple }} />
34 -
            <Card style={{ height: 150, backgroundColor: 'powderblue' }} />
35 -
          </Grid>
36 -
        </Section>
37 -
      </ThemedView>
47 +
        <MoodTrendWidget />
48 +

49 +
        <InsightCard
50 +
          category="Gatilhos"
51 +
          body="Trabalho representa 45% dos registros."
52 +
          metric="45%"
53 +
          accent="neutral"
54 +
        />
55 +
      </Section>
38 56
    </ScreenLayout>
39 -
  )
57 +
  );
40 58
}
59 +

60 +
const styles = StyleSheet.create({
61 +
  scrollView: {
62 +
    flex: 1,
63 +
    backgroundColor: Colors.light.background,
64 +
  },
65 +
  contentContainer: {
66 +
    padding: Spacing.containerPadding,
67 +
    paddingBottom: 40,
68 +
  },
69 +

70 +
  // Hero card
71 +
  heroCard: {
72 +
    backgroundColor: Colors.light.textBlack,
73 +
    borderRadius: BorderRadius.lg,
74 +
    padding: 20,
75 +
    marginBottom: Spacing.sectionGap,
76 +
  },
77 +
  heroLabel: {
78 +
    fontSize: 11,
79 +
    fontWeight: "600",
80 +
    letterSpacing: 0.8,
81 +
    textTransform: "uppercase",
82 +
    color: Colors.light.tint,
83 +
    marginBottom: 8,
84 +
  },
85 +
  heroScoreRow: {
86 +
    flexDirection: "row",
87 +
    alignItems: "center",
88 +
    gap: 10,
89 +
  },
90 +
  heroScore: {
91 +
    ...Typography.headlineXg,
92 +
    fontSize: 52,
93 +
    color: "#ffffff",
94 +
  },
95 +
  heroTrendIcon: {
96 +
    marginTop: 6,
97 +
  },
98 +
  heroDivider: {
99 +
    height: StyleSheet.hairlineWidth,
100 +
    backgroundColor: "rgba(255,255,255,0.12)",
101 +
    marginVertical: 16,
102 +
  },
103 +
  heroStats: {
104 +
    flexDirection: "row",
105 +
    gap: Spacing.sectionGap,
106 +
  },
107 +
  heroStatItem: {
108 +
    flex: 1,
109 +
  },
110 +
  heroStatLabel: {
111 +
    fontSize: 12,
112 +
    color: "rgba(255,255,255,0.45)",
113 +
    fontWeight: "500",
114 +
    marginBottom: 4,
115 +
  },
116 +
  heroStatValue: {
117 +
    ...Typography.bodyLg,
118 +
    fontSize: 20,
119 +
    color: "#ffffff",
120 +
  },
121 +

122 +
  // Insight cards
123 +
  insightCardInner: {
124 +
    flexDirection: "row",
125 +
    alignItems: "center",
126 +
    justifyContent: "space-between",
127 +
    gap: 12,
128 +
  },
129 +
  insightCardLeft: {
130 +
    flex: 1,
131 +
    gap: 6,
132 +
  },
133 +
  insightCategory: {
134 +
    fontSize: 11,
135 +
    fontWeight: "600",
136 +
    letterSpacing: 0.8,
137 +
    textTransform: "uppercase",
138 +
    color: Colors.light.textSecondary,
139 +
  },
140 +
  insightBody: {
141 +
    ...Typography.bodyMd,
142 +
    color: Colors.light.text,
143 +
  },
144 +
  insightMetric: {
145 +
    ...Typography.headlineLg,
146 +
    fontSize: 22,
147 +
  },
148 +
});
/dev/null → frontend/components/insights/InsightCard.tsx
1 +
import React from "react";
2 +
import { View, Text, StyleSheet } from "react-native";
3 +

4 +
import { Card } from "@/components/ui/Cards";
5 +
import { Colors, Spacing, Typography } from "@/constants/theme";
6 +

7 +
export type MetricAccent = "green" | "purple" | "neutral";
8 +

9 +
const metricColor: Record<MetricAccent, string> = {
10 +
  green: Colors.light.tint,
11 +
  purple: Colors.light.accentDarkPurple,
12 +
  neutral: Colors.light.text,
13 +
};
14 +

15 +
interface InsightCardProps {
16 +
  category: string;
17 +
  body: React.ReactNode;
18 +
  metric: string;
19 +
  accent?: MetricAccent;
20 +
}
21 +

22 +
export const InsightCard: React.FC<InsightCardProps> = ({
23 +
  category,
24 +
  body,
25 +
  metric,
26 +
  accent = "neutral",
27 +
}) => (
28 +
  <Card style={styles.cardContainer}>
29 +
    <View style={styles.inner}>
30 +
      <View style={styles.left}>
31 +
        <Text style={styles.category}>{category}</Text>
32 +
        {typeof body === "string" ? (
33 +
          <Text style={styles.body}>{body}</Text>
34 +
        ) : (
35 +
          body
36 +
        )}
37 +
      </View>
38 +
      <Text style={[styles.metric, { color: metricColor[accent] }]}>
39 +
        {metric}
40 +
      </Text>
41 +
    </View>
42 +
  </Card>
43 +
);
44 +

45 +
interface HighlightProps {
46 +
  children: React.ReactNode;
47 +
  color?: string;
48 +
}
49 +

50 +
export const Highlight: React.FC<HighlightProps> = ({
51 +
  children,
52 +
  color = Colors.light.tint,
53 +
}) => <Text style={{ color, fontWeight: "700" }}>{children}</Text>;
54 +

55 +
const styles = StyleSheet.create({
56 +
  cardContainer: {
57 +
    padding: Spacing.cardGap,
58 +
  },
59 +
  inner: {
60 +
    flexDirection: "row",
61 +
    alignItems: "center",
62 +
    justifyContent: "space-between",
63 +
    gap: 12,
64 +
  },
65 +
  left: {
66 +
    flex: 1,
67 +
    gap: 6,
68 +
  },
69 +
  category: {
70 +
    fontSize: 11,
71 +
    fontWeight: "600",
72 +
    letterSpacing: 0.8,
73 +
    textTransform: "uppercase",
74 +
    color: Colors.light.textSecondary,
75 +
  },
76 +
  body: {
77 +
    ...Typography.bodyMd,
78 +
    color: Colors.light.text,
79 +
  },
80 +
  metric: {
81 +
    ...Typography.headlineLg,
82 +
    fontSize: 22,
83 +
  },
84 +
});
/dev/null → frontend/components/insights/WeeklyResume.tsx
1 +
import React from "react";
2 +
import { View, Text, StyleSheet } from "react-native";
3 +
import { Colors, Typography, Spacing, BorderRadius } from "@/constants/theme";
4 +

5 +
interface WeeklyResumeProps {
6 +
  avgMood: string;
7 +
  stats: { label: string; value: string }[];
8 +
}
9 +

10 +
export const WeeklyResume: React.FC<WeeklyResumeProps> = ({
11 +
  avgMood,
12 +
  stats,
13 +
}) => {
14 +
  return (
15 +
    <View style={styles.heroCard}>
16 +
      <Text style={styles.heroLabel}>Resumo Semanal</Text>
17 +

18 +
      <View style={styles.heroScoreRow}>
19 +
        <Text style={styles.heroScore}>{avgMood}</Text>
20 +
      </View>
21 +

22 +
      <View style={styles.heroDivider} />
23 +

24 +
      <View style={styles.heroStats}>
25 +
        {stats.map((stat) => (
26 +
          <View key={stat.label} style={styles.heroStatItem}>
27 +
            <Text style={styles.heroStatLabel}>{stat.label}</Text>
28 +
            <Text style={styles.heroStatValue}>{stat.value}</Text>
29 +
          </View>
30 +
        ))}
31 +
      </View>
32 +
    </View>
33 +
  );
34 +
};
35 +

36 +
const styles = StyleSheet.create({
37 +
  heroCard: {
38 +
    backgroundColor: Colors.light.black,
39 +
    borderRadius: BorderRadius.lg,
40 +
    padding: 20,
41 +
    marginBottom: Spacing.sectionGap,
42 +
  },
43 +
  heroLabel: {
44 +
    fontSize: 11,
45 +
    fontWeight: "600",
46 +
    letterSpacing: 0.8,
47 +
    textTransform: "uppercase",
48 +
    color: Colors.light.tint,
49 +
    marginBottom: 8,
50 +
  },
51 +
  heroScoreRow: {
52 +
    flexDirection: "row",
53 +
    alignItems: "center",
54 +
    gap: 10,
55 +
  },
56 +
  heroScore: {
57 +
    ...Typography.headlineXg,
58 +
    fontSize: 52,
59 +
    color: "#ffffff",
60 +
  },
61 +
  heroTrendIcon: {
62 +
    marginTop: 6,
63 +
  },
64 +
  heroDivider: {
65 +
    height: StyleSheet.hairlineWidth,
66 +
    backgroundColor: "rgba(255,255,255,0.12)",
67 +
    marginVertical: 16,
68 +
  },
69 +
  heroStats: {
70 +
    flexDirection: "row",
71 +
    gap: Spacing.sectionGap,
72 +
  },
73 +
  heroStatItem: {
74 +
    flex: 1,
75 +
  },
76 +
  heroStatLabel: {
77 +
    fontSize: 12,
78 +
    color: "rgba(255,255,255,0.45)",
79 +
    fontWeight: "500",
80 +
    marginBottom: 4,
81 +
  },
82 +
  heroStatValue: {
83 +
    ...Typography.bodyLg,
84 +
    fontSize: 20,
85 +
    color: "#ffffff",
86 +
  },
87 +
});
frontend/constants/theme.ts
1 1
/**
2 2
 * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
3 3
 * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
4 4
 */
5 5

30 30
const PILL_INDIGO = "#e0e7ff";
31 31
const PILL_PURPLE = "#f3e8ff";
32 32

33 33
const tintColorLight = "#12ED5C";
34 34
const tintColorSecondary = "#618A70";
35 -
const black = "#0F172A";
36 -
const gray = "#475569";
37 -
const lightGray = "#64748B";
35 +
const GRAY = "#475569";
36 +
const LIGHT_GRAY = "#64748B";
37 +
const LIGHT_SLATE_GRAY = "#94A3B8";
38 38

39 39
export const Colors = {
40 40
  light: {
41 41
    // Primary Actions & Focus
42 42
    text: TEXT_PRIMARY,
72 72

73 73
    // Semantic Accents (for categorization)
74 74
    accentBlue: PILL_BLUE,
75 75
    accentIndigo: PILL_INDIGO,
76 76
    accentPurple: PILL_PURPLE,
77 +
    accentDarkPurple: "#7F77DD",
78 +

79 +
    // Some grays
80 +
    gray: GRAY,
81 +
    lightGray: LIGHT_GRAY,
82 +
    lightSlateGray: LIGHT_SLATE_GRAY,
83 +
    black: PRIMARY_DARK,
77 84

78 85
    // Disabled / Secondary
79 86
    disabled: "#cbd5e1",
80 87
    divider: BORDER_SUBTLE,
81 88
    sliderTracking: "#E2E8F0",
82 89
    sliderLabels: "#94a3b8",
90 +
    warning: "orange",
83 91
    success: "#22c55e",
84 92
    danger: "#EF4444",
85 93
    textBlack: PRIMARY_DARK,
86 94
  },
87 95
};
225 233
  typography: Typography,
226 234
  spacing: Spacing,
227 235
  shadows: Shadows,
228 236
  borderRadius: BorderRadius,
229 237
};