import { useCallback, useEffect, useRef, useState } from "react"; import { View, Text, FlatList, Pressable, ActivityIndicator, Alert, } from "react-native"; import { router, useFocusEffect } from "expo-router"; import * as SecureStore from "expo-secure-store"; import { getIncidents, markIncidentPushed } from "@/lib/db"; import { pushIncidentToGit, loadGitConfig } from "@/lib/git"; import type { GitConfig } from "@/lib/git"; import { useSettings } from "@/hooks/useSettings"; import type { Incident } from "@/types/incident"; import { Colors } from "@/constants/theme"; function gitDotColor(inc: Incident): string { if (!inc.gitPushedAt) return Colors.danger; if (inc.gitPushedAt < inc.updatedAt) return Colors.warning; return Colors.success; } export default function HomeScreen() { const [incidents, setIncidents] = useState([]); const [loading, setLoading] = useState(true); const [gitConfig, setGitConfig] = useState(null); const [mdTemplate, setMdTemplate] = useState(undefined); const [selectionMode, setSelectionMode] = useState(false); const [selected, setSelected] = useState>(new Set()); const [bulkPushing, setBulkPushing] = useState(false); const [bulkProgress, setBulkProgress] = useState({ done: 0, total: 0 }); 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]); useFocusEffect( useCallback(() => { load(); }, []) ); async function load() { setLoading(true); const [data, config, tpl] = await Promise.all([ getIncidents(100), loadGitConfig(), SecureStore.getItemAsync("md_template"), ]); setIncidents(data); setGitConfig(config); setMdTemplate(tpl ?? undefined); setLoading(false); } function enterSelectionMode(id: string) { setSelectionMode(true); setSelected(new Set([id])); } function toggleSelection(id: string) { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } function exitSelectionMode() { setSelectionMode(false); setSelected(new Set()); } function selectAllUnpushed() { const ids = incidents .filter((i) => !i.gitPushedAt || i.gitPushedAt < i.updatedAt) .map((i) => i.id); setSelected(new Set(ids)); } const unpushedCount = incidents.filter( (i) => !i.gitPushedAt || i.gitPushedAt < i.updatedAt ).length; async function handleBulkPush() { if (!gitConfig) { Alert.alert("Git not configured", "Set up a git provider in Settings first."); return; } const toPush = incidents.filter((i) => selected.has(i.id)); if (toPush.length === 0) return; setBulkPushing(true); setBulkProgress({ done: 0, total: toPush.length }); let ok = 0; let fail = 0; const now = new Date().toISOString(); for (let i = 0; i < toPush.length; i++) { const inc = toPush[i]; const result = await pushIncidentToGit(inc, gitConfig, mdTemplate); if (result.success) { await markIncidentPushed(inc.id); setIncidents((prev) => prev.map((x) => (x.id === inc.id ? { ...x, gitPushedAt: now } : x)) ); ok++; } else { fail++; } setBulkProgress({ done: i + 1, total: toPush.length }); } setBulkPushing(false); exitSelectionMode(); if (fail === 0) { Alert.alert("Done", `${ok} incident${ok > 1 ? "s" : ""} pushed.`); } else { Alert.alert("Partial", `${ok} pushed, ${fail} failed.`); } } const gitConfigured = gitConfig !== null; return ( {loading ? ( ) : ( item.id} contentContainerStyle={{ padding: 16, paddingBottom: 96 }} ListEmptyComponent={ No incidents. Press + to log one. } renderItem={({ item }) => { const isSelected = selected.has(item.id); return ( selectionMode ? toggleSelection(item.id) : router.push(`/incident/${item.id}`) } onLongPress={() => !selectionMode && enterSelectionMode(item.id)} style={{ backgroundColor: isSelected ? Colors.surface2 : Colors.surface, borderRadius: 8, padding: 16, marginBottom: 10, borderLeftWidth: 3, borderLeftColor: item.status === "open" ? Colors.warning : Colors.success, flexDirection: "row", alignItems: "center", }} > {item.title || "Untitled"} {gitConfigured && !selectionMode && ( )} {item.status} {item.service || "—"} · {item.createdAt.slice(0, 10)} {selectionMode && ( {isSelected && ( )} )} ); }} /> )} {/* FAB — hidden in selection mode */} {!selectionMode && ( 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, }} > + )} {/* Selection mode action bar */} {selectionMode && ( {/* Quick-select shortcuts */} {gitConfigured && unpushedCount > 0 && ( Select unpushed ({unpushedCount}) )} setSelected(new Set(incidents.map((i) => i.id)))} style={{ flex: 1, borderRadius: 6, borderWidth: 1, borderColor: Colors.border, padding: 8, alignItems: "center", }} > Select all ({incidents.length}) {/* Main actions */} Cancel {bulkPushing ? `Pushing ${bulkProgress.done}/${bulkProgress.total}…` : `Push ${selected.size > 0 ? selected.size : ""} selected`} )} ); }