frontend: display generated trigger pattern insights
Parents:
25815193 file(s) changed
- frontend/app/(tabs)/insights.tsx +2 -6
- frontend/components/insights/TriggerPatternWidget.tsx +90 -0
- frontend/hooks/useTrigger.queries.ts +8 -0
frontend/app/(tabs)/insights.tsx
1 1
import { View, Text, ScrollView, StyleSheet } from "react-native";
2 2
3 3
import { ThemedText } from "@/components/misc/themed-text";
4 4
import { ThemedView } from "@/components/misc/themed-view";
5 5
import { ScreenLayout } from "@/components/ui/ScreenLayout";
▸ 3 unchanged lines
9 9
import { useThemeColor } from "@/hooks/use-theme-color";
10 10
import { Spacing, BorderRadius, Colors, Typography } from "@/constants/theme";
11 11
import { WeeklyResume } from "@/components/insights/WeeklyResume";
12 12
import { InsightCard, Highlight } from "@/components/insights/InsightCard";
13 13
import { MoodTrendWidget } from "@/components/insights/MoodTrendWidget";
14 +
import { TriggerPatternWidget } from "@/components/insights/TriggerPatternWidget";
14 15
15 16
export default function Insights() {
16 17
const { user } = useAuth();
17 18
18 19
return (
▸ 25 unchanged lines
44 45
accent="green"
45 46
/>
46 47
47 48
<MoodTrendWidget />
48 49
49 -
<InsightCard
50 -
category="Gatilhos"
51 -
body="Trabalho representa 45% dos registros."
52 -
metric="45%"
53 -
accent="neutral"
54 -
/>
50 +
<TriggerPatternWidget />
55 51
</Section>
56 52
</ScreenLayout>
57 53
);
58 54
}
59 55
▸ 84 unchanged lines
144 140
insightMetric: {
145 141
...Typography.headlineLg,
146 142
fontSize: 22,
147 143
},
148 144
});
frontend/components/insights/TriggerPatternWidget.tsx
@@ -0,0 +1,90 @@
1 + import React from "react";
2 + import { Text, View, StyleSheet, ActivityIndicator } from "react-native";
3 + import { MaterialIcons } from "@expo/vector-icons";
4 + import { Card } from "@/components/ui/Cards";
5 + import { useInsight } from "@/hooks/useInsights.queries";
6 + import { Colors, Spacing } from "@/constants/theme";
7 + import { getTriggerCategory } from "@/constants/trigger-categories";
8 + import { InsightCard } from "./InsightCard";
9 +
10 + export const TriggerPatternWidget: React.FC = () => {
11 + const { data: insight, isLoading } = useInsight("TRIGGER_PATTERN");
12 +
13 + if (isLoading) {
14 + return (
15 + <Card style={styles.container}>
16 + <ActivityIndicator />
17 + </Card>
18 + );
19 + }
20 +
21 + if (!insight) {
22 + return (
23 + <InsightCard
24 + category="Gatilhos"
25 + body={
26 + <Text style={styles.empty}>
27 + Registre gatilhos com mais frequência para identificar padrões.
28 + </Text>
29 + }
30 + accent="neutral"
31 + metric="—"
32 + />
33 + );
34 + }
35 +
36 + const metadata = insight.metadata as {
37 + topCategory: string;
38 + topCount: number;
39 + total: number;
40 + distribution: Record<string, number>;
41 + };
42 +
43 + const pct =
44 + metadata.total > 0
45 + ? Math.round((metadata.topCount / metadata.total) * 100)
46 + : 0;
47 +
48 + const topMeta = getTriggerCategory(metadata.topCategory);
49 +
50 + return (
51 + <InsightCard
52 + category="Gatilhos"
53 + body={
54 + <View style={styles.bodyRow}>
55 + <Text style={styles.bodyText}>{insight.body}</Text>
56 + </View>
57 + }
58 + metric={`${pct}%`}
59 + accent="neutral"
60 + />
61 + );
62 + };
63 +
64 + const styles = StyleSheet.create({
65 + container: {
66 + padding: Spacing.cardGap,
67 + },
68 + bodyRow: {
69 + flexDirection: "row",
70 + alignItems: "center",
71 + gap: 6,
72 + },
73 + iconWrap: {
74 + width: 22,
75 + height: 22,
76 + borderRadius: 11,
77 + alignItems: "center",
78 + justifyContent: "center",
79 + },
80 + bodyText: {
81 + flex: 1,
82 + fontSize: 14,
83 + color: Colors.light.text,
84 + lineHeight: 20,
85 + },
86 + empty: {
87 + color: Colors.light.lightSlateGray,
88 + marginTop: 4,
89 + },
90 + });
frontend/hooks/useTrigger.queries.ts
1 1
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2 2
3 3
import { apiClient, Trigger, CreateTriggerPayload } from "@/lib/api";
4 +
import { insightKeys } from "@/hooks/useInsights.queries";
4 5
5 6
export const triggerKeys = {
6 7
all: () => ["triggers"] as const,
7 8
lists: () => [...triggerKeys.all(), "list"] as const,
8 9
list: (filters?: TriggerFilters) =>
▸ 69 unchanged lines
78 79
},
79 80
80 81
// On success, replace optimistic record with real one from server
81 82
onSuccess: () => {
82 83
queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
84 +
// Delay to allow the async queue job to finish writing the new insight row
85 +
setTimeout(() => {
86 +
queryClient.invalidateQueries({ queryKey: insightKeys.byType("TRIGGER_PATTERN") });
87 +
}, 2000);
83 88
},
84 89
});
85 90
};
86 91
87 92
// Delete a mood entry with optimistic removal
▸ 20 unchanged lines
108 113
}
109 114
},
110 115
111 116
onSuccess: () => {
112 117
queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
118 +
setTimeout(() => {
119 +
queryClient.invalidateQueries({ queryKey: insightKeys.byType("TRIGGER_PATTERN") });
120 +
}, 2000);
113 121
},
114 122
});
115 123
};