Files
SheetHappens/app/index.tsx
T
billisdead 418eb1daa1 Add MVP features: edit mode, voice input, Gitea push, service autocomplete
- new.tsx: edit mode via editId param, per-field voice dictation (MIC/STOP),
  service autocomplete dropdown from DB history
- incident/[id].tsx: Push to Gitea action reads SecureStore config + md template
- index.tsx: homeView=capture redirects to /new on first mount
- _layout.tsx: onboarding gate wired (useSettings + router.replace)
- ci: GitHub Actions workflow builds debug APK via expo prebuild + Gradle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 12:03:44 +02:00

159 lines
4.4 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import {
View,
Text,
FlatList,
Pressable,
ActivityIndicator,
} from "react-native";
import { router } from "expo-router";
import { getIncidents } from "@/lib/db";
import { useSettings } from "@/hooks/useSettings";
import type { Incident } from "@/types/incident";
import { Colors } from "@/constants/theme";
export default function HomeScreen() {
const [incidents, setIncidents] = useState<Incident[]>([]);
const [loading, setLoading] = useState(true);
const { settings, ready } = useSettings();
const didRedirect = useRef(false);
useEffect(() => {
if (!ready || didRedirect.current) return;
if (settings.homeView === "capture") {
didRedirect.current = true;
router.push("/new");
}
}, [ready, settings.homeView]);
useEffect(() => {
loadIncidents();
}, []);
async function loadIncidents() {
setLoading(true);
const data = await getIncidents(10);
setIncidents(data);
setLoading(false);
}
return (
<View style={{ flex: 1, backgroundColor: Colors.bg }}>
{loading ? (
<ActivityIndicator
color={Colors.primary}
style={{ marginTop: 48 }}
/>
) : (
<FlatList
data={incidents}
keyExtractor={(item) => item.id}
contentContainerStyle={{ padding: 16, paddingBottom: 96 }}
ListEmptyComponent={
<Text
style={{
color: Colors.textDim,
textAlign: "center",
marginTop: 64,
fontSize: 15,
}}
>
No incidents. Press + to log one.
</Text>
}
renderItem={({ item }) => (
<Pressable
onPress={() => router.push(`/incident/${item.id}`)}
style={{
backgroundColor: Colors.surface,
borderRadius: 8,
padding: 16,
marginBottom: 10,
borderLeftWidth: 3,
borderLeftColor:
item.status === "open" ? Colors.warning : Colors.success,
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 4,
}}
>
<Text
style={{
color: Colors.text1,
fontSize: 15,
fontWeight: "600",
flex: 1,
}}
numberOfLines={1}
>
{item.title || "Untitled"}
</Text>
<View
style={{
backgroundColor:
item.status === "open"
? "#f59e0b20"
: "#22c55e20",
borderRadius: 4,
paddingHorizontal: 8,
paddingVertical: 2,
marginLeft: 8,
}}
>
<Text
style={{
color:
item.status === "open"
? Colors.warning
: Colors.success,
fontSize: 11,
fontWeight: "700",
textTransform: "uppercase",
}}
>
{item.status}
</Text>
</View>
</View>
<Text
style={{ color: Colors.text2, fontSize: 12 }}
numberOfLines={1}
>
{item.service || "—"} · {item.createdAt.slice(0, 10)}
</Text>
</Pressable>
)}
/>
)}
{/* FAB */}
<Pressable
onPress={() => router.push("/new")}
style={{
position: "absolute",
bottom: 24,
right: 24,
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: Colors.primary,
alignItems: "center",
justifyContent: "center",
elevation: 6,
}}
>
<Text
style={{ color: "#fff", fontSize: 28, lineHeight: 32 }}
>
+
</Text>
</Pressable>
</View>
);
}