frontend: introduce a empty state component
Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago
a13a1c0382a8d1ae8d553f0a91d6e5716c967b31 Browse filesParents
d28d4c0Changed files
3
Additions
+66
Deletions
-10
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";
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} />;
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";
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 });
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
))}
155 161
fontSize: 11,
156 162
fontWeight: 600,
157 163
lineHeight: 16.5,
158 164
},
159 165
});
/dev/null → frontend/components/ui/EmptyState.tsx
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 +
});