Initial scaffold: Expo SDK 56, expo-router, expo-sqlite, NativeWind
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
Pressable,
|
||||
Alert,
|
||||
Share,
|
||||
} from "react-native";
|
||||
import { useLocalSearchParams, router, useNavigation } from "expo-router";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import { getIncidentById, updateIncident } from "@/lib/db";
|
||||
import { renderMarkdown } from "@/lib/markdown";
|
||||
import type { Incident } from "@/types/incident";
|
||||
import { Colors } from "@/constants/theme";
|
||||
|
||||
const SECTION_LABEL = {
|
||||
color: Colors.text2,
|
||||
fontSize: 11,
|
||||
fontWeight: "700" as const,
|
||||
textTransform: "uppercase" as const,
|
||||
letterSpacing: 0.8,
|
||||
marginBottom: 6,
|
||||
marginTop: 20,
|
||||
} as const;
|
||||
|
||||
const SECTION_CONTENT = {
|
||||
color: Colors.text1,
|
||||
fontSize: 14,
|
||||
fontFamily: "monospace",
|
||||
backgroundColor: Colors.surface,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
lineHeight: 20,
|
||||
} as const;
|
||||
|
||||
export default function IncidentDetailScreen() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const navigation = useNavigation();
|
||||
const [incident, setIncident] = useState<Incident | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
getIncidentById(id).then((inc) => {
|
||||
setIncident(inc);
|
||||
if (inc) {
|
||||
navigation.setOptions({ title: inc.title || "Incident" });
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
async function handleResolve() {
|
||||
if (!incident) return;
|
||||
await updateIncident(incident.id, { status: "resolved" });
|
||||
setIncident({ ...incident, status: "resolved" });
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
if (!incident) return;
|
||||
const md = renderMarkdown(incident);
|
||||
try {
|
||||
await Share.share({ message: md, title: incident.title });
|
||||
} catch {
|
||||
await Clipboard.setStringAsync(md);
|
||||
Alert.alert("Copied", "Markdown copied to clipboard.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!incident) {
|
||||
return (
|
||||
<View
|
||||
style={{ flex: 1, backgroundColor: Colors.bg, alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Text style={{ color: Colors.textDim }}>Loading…</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={{ flex: 1, backgroundColor: Colors.bg }}
|
||||
contentContainerStyle={{ padding: 16, paddingBottom: 32 }}
|
||||
>
|
||||
{/* Status badge */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, marginBottom: 4 }}>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor:
|
||||
incident.status === "open" ? "#f59e0b20" : "#22c55e20",
|
||||
borderRadius: 6,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: incident.status === "open" ? Colors.warning : Colors.success,
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
{incident.status}
|
||||
</Text>
|
||||
</View>
|
||||
{incident.service ? (
|
||||
<Text style={{ color: Colors.textDim, fontSize: 12 }}>
|
||||
{incident.service}
|
||||
</Text>
|
||||
) : null}
|
||||
<Text style={{ color: Colors.textDim, fontSize: 12 }}>
|
||||
{incident.createdAt.slice(0, 16).replace("T", " ")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: Colors.text1,
|
||||
fontSize: 22,
|
||||
fontWeight: "700",
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
{incident.title || "Untitled"}
|
||||
</Text>
|
||||
|
||||
{incident.tags.length > 0 && (
|
||||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 6, marginTop: 8 }}>
|
||||
{incident.tags.map((tag) => (
|
||||
<View
|
||||
key={tag}
|
||||
style={{
|
||||
backgroundColor: Colors.surface2,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.text2, fontSize: 12 }}>{tag}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text style={SECTION_LABEL}>Symptom</Text>
|
||||
<Text style={SECTION_CONTENT}>{incident.symptom || "—"}</Text>
|
||||
|
||||
<Text style={SECTION_LABEL}>Root Cause</Text>
|
||||
<Text style={SECTION_CONTENT}>{incident.rootCause || "—"}</Text>
|
||||
|
||||
<Text style={SECTION_LABEL}>Fix Applied</Text>
|
||||
<Text style={SECTION_CONTENT}>{incident.fix || "—"}</Text>
|
||||
|
||||
{/* Actions */}
|
||||
<View style={{ gap: 10, marginTop: 28 }}>
|
||||
{incident.status === "open" && (
|
||||
<Pressable
|
||||
onPress={handleResolve}
|
||||
style={{
|
||||
backgroundColor: "#22c55e20",
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.success,
|
||||
borderRadius: 10,
|
||||
padding: 14,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.success, fontWeight: "700", fontSize: 15 }}>
|
||||
Mark as Resolved
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
<Pressable
|
||||
onPress={handleExport}
|
||||
style={{
|
||||
backgroundColor: Colors.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.border,
|
||||
borderRadius: 10,
|
||||
padding: 14,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.text1, fontWeight: "600", fontSize: 15 }}>
|
||||
Export Markdown
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={() => router.push({ pathname: "/new", params: { editId: incident.id } })}
|
||||
style={{
|
||||
backgroundColor: Colors.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.border,
|
||||
borderRadius: 10,
|
||||
padding: 14,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.text2, fontWeight: "600", fontSize: 15 }}>
|
||||
Edit
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user