frontend: introduce a empty state component
Parents:
d28d4c03 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 + });