eletrotupi / tcc/ commit / a13a1c0

frontend: introduce a empty state component

Pedro Lucas Porcellis porcellis@eletrotupi.com 29 days ago a13a1c0382a8d1ae8d553f0a91d6e5716c967b31
Parents: d28d4c0
3 file(s) changed
  • frontend/app/(tabs)/history.tsx +19 -10
  • frontend/app/(tabs)/new/index.tsx +6 -0
  • frontend/components/ui/EmptyState.tsx +41 -0
frontend/app/(tabs)/history.tsx
1 1
import { useState } from "react";
2 2
import { ScrollView, View, Text } from "react-native";
3 3
import { ScreenLayout } from "@/components/ui/ScreenLayout";
4 4
import { Section, SectionHeader } from "@/components/ui/Sections";
5 5
import { FilterPills } from "@/components/ui/FilterPills";
▸ 4 unchanged lines
10 10
import { SleepRecordCard } from "@/components/history/SleepRecordCard";
11 11
import { TriggerHistoryCard } from "@/components/history/TriggerCard";
12 12
import { MedicineHistoryCard } from "@/components/history/MedicineHistoryCard";
13 13
import { AppointmentHistoryCard } from "@/components/history/AppointmentHistoryCard";
14 14
import { ActivityHistoryCard } from "@/components/history/ActivityHistoryCard";
15 +
import { EmptyState } from "@/components/ui/EmptyState";
15 16

16 17
function HistoryCardRenderer({ card }: { card: HistoryCard }) {
17 18
  switch (card.category) {
18 19
    case "mood":
19 20
      return <MoodHistoryCard card={card as any} />;
▸ 29 unchanged lines
49 50
      showNotificationBadge={true}
50 51
    >
51 52
      <ScrollView>
52 53
        <FilterPills active={activeFilter} onChange={setActiveFilter} />
53 54

54 -
        {Object.entries(grouped).map(([date, cards]) => (
55 -
          <Section key={date}>
56 -
            <SectionHeader title={date} />
57 -
            <View>
58 -
              {cards.map((card) => (
59 -
                <HistoryCardRenderer key={card.id} card={card} />
60 -
              ))}
61 -
            </View>
62 -
          </Section>
63 -
        ))}
55 +
        {Object.keys(grouped).length === 0 ? (
56 +
          <EmptyState
57 +
            title="Nenhum registro encontrado"
58 +
            subtitle="Tente mudar o filtro ou adicione um novo registro."
59 +
            icon="time-outline"
60 +
          />
61 +
        ) : (
62 +
          Object.entries(grouped).map(([date, cards]) => (
63 +
            <Section key={date}>
64 +
              <SectionHeader title={date} />
65 +
              <View>
66 +
                {cards.map((card) => (
67 +
                  <HistoryCardRenderer key={card.id} card={card} />
68 +
                ))}
69 +
              </View>
70 +
            </Section>
71 +
          ))
72 +
        )}
64 73
      </ScrollView>
65 74
    </ScreenLayout>
66 75
  );
67 76
}
frontend/app/(tabs)/new/index.tsx
1 1
import { useEffect, useState } from "react";
2 2
import { StyleSheet, View, Text, ActivityIndicator } from "react-native";
3 3

4 4
import { ThemedText } from "@/components/misc/themed-text";
5 5
import { ThemedView } from "@/components/misc/themed-view";
▸ 19 unchanged lines
25 25
import { useThemeColor, useMoodEntries } from "@/hooks";
26 26

27 27
import MoodEntryLog from "@/components/ui/MoodEntryLog";
28 28
import { DailyEnergyWidget } from "@/components/ui/DailyEnergyWidget";
29 29
import { DailySleepWidget } from "@/components/ui/DailySleepWidget";
30 +
import { EmptyState } from "@/components/ui/EmptyState";
30 31

31 32
export default function New() {
32 33
  const { user } = useAuth();
33 34
  const [mood, setMood] = useState<string>("good");
34 35
  const { data, isLoading } = useMoodEntries({ limit: 5 });
▸ 69 unchanged lines
104 105
      <Section>
105 106
        <SectionHeader title="Registros recentes" />
106 107

107 108
        {isLoading ? (
108 109
          <ActivityIndicator />
110 +
        ) : recentEntries.length === 0 ? (
111 +
          <EmptyState
112 +
            title="Nenhum registro ainda"
113 +
            subtitle="Seus registros de humor aparecerão aqui."
114 +
          />
109 115
        ) : (
110 116
          <Col gap={8}>
111 117
            {recentEntries.map((entry) => (
112 118
              <MoodEntryLog key={entry.id} entry={entry} />
113 119
            ))}
▸ 41 unchanged lines
155 161
    fontSize: 11,
156 162
    fontWeight: 600,
157 163
    lineHeight: 16.5,
158 164
  },
159 165
});
frontend/components/ui/EmptyState.tsx
@@ -0,0 +1,41 @@
1 + import { View, Text, StyleSheet } from "react-native";
2 + import { Ionicons } from "@expo/vector-icons";
3 + import { Colors, Typography, Spacing } from "@/constants/theme";
4 +
5 + interface EmptyStateProps {
6 + title: string;
7 + subtitle?: string;
8 + icon?: keyof typeof Ionicons.glyphMap;
9 + }
10 +
11 + export function EmptyState({
12 + title,
13 + subtitle,
14 + icon = "leaf-outline",
15 + }: EmptyStateProps) {
16 + return (
17 + <View style={styles.container}>
18 + <Ionicons name={icon} size={40} color={Colors.light.textSecondary} />
19 + <Text style={styles.title}>{title}</Text>
20 + {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
21 + </View>
22 + );
23 + }
24 +
25 + const styles = StyleSheet.create({
26 + container: {
27 + alignItems: "center",
28 + paddingVertical: 32,
29 + gap: Spacing.inlineGapSm,
30 + },
31 + title: {
32 + ...Typography.bodyLg,
33 + color: Colors.light.text,
34 + textAlign: "center",
35 + },
36 + subtitle: {
37 + ...Typography.bodyMd,
38 + color: Colors.light.textSecondary,
39 + textAlign: "center",
40 + },
41 + });