211 lines
5.7 KiB
TypeScript
211 lines
5.7 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
TextInput,
|
|
ScrollView,
|
|
Pressable,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
Alert,
|
|
} from "react-native";
|
|
import { router } from "expo-router";
|
|
import { createIncident } from "@/lib/db";
|
|
import { Colors } from "@/constants/theme";
|
|
import type { IncidentDraft } from "@/types/incident";
|
|
|
|
const FIELD_STYLE = {
|
|
backgroundColor: Colors.surface,
|
|
borderColor: Colors.border,
|
|
borderWidth: 1,
|
|
borderRadius: 8,
|
|
color: Colors.text1,
|
|
padding: 12,
|
|
fontSize: 14,
|
|
marginBottom: 16,
|
|
} as const;
|
|
|
|
const MONO_FIELD_STYLE = {
|
|
...FIELD_STYLE,
|
|
fontFamily: "monospace",
|
|
minHeight: 80,
|
|
textAlignVertical: "top" as const,
|
|
} as const;
|
|
|
|
const LABEL_STYLE = {
|
|
color: Colors.text2,
|
|
fontSize: 12,
|
|
fontWeight: "600" as const,
|
|
textTransform: "uppercase" as const,
|
|
letterSpacing: 0.5,
|
|
marginBottom: 6,
|
|
} as const;
|
|
|
|
export default function NewIncidentScreen() {
|
|
const [form, setForm] = useState<IncidentDraft>({
|
|
title: "",
|
|
service: "",
|
|
symptom: "",
|
|
rootCause: "",
|
|
fix: "",
|
|
status: "open",
|
|
tags: [],
|
|
});
|
|
const [tagInput, setTagInput] = useState("");
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
function set(key: keyof IncidentDraft) {
|
|
return (value: string) => setForm((f) => ({ ...f, [key]: value }));
|
|
}
|
|
|
|
function handleTagInputEnd() {
|
|
const trimmed = tagInput.trim();
|
|
if (trimmed && !form.tags.includes(trimmed)) {
|
|
setForm((f) => ({ ...f, tags: [...f.tags, trimmed] }));
|
|
}
|
|
setTagInput("");
|
|
}
|
|
|
|
function removeTag(tag: string) {
|
|
setForm((f) => ({ ...f, tags: f.tags.filter((t) => t !== tag) }));
|
|
}
|
|
|
|
async function handleSave() {
|
|
if (!form.title.trim()) {
|
|
Alert.alert("Required", "Title is required.");
|
|
return;
|
|
}
|
|
setSaving(true);
|
|
try {
|
|
await createIncident(form);
|
|
router.back();
|
|
} catch (e) {
|
|
Alert.alert("Error", "Failed to save incident.");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<KeyboardAvoidingView
|
|
style={{ flex: 1, backgroundColor: Colors.bg }}
|
|
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
|
>
|
|
<ScrollView
|
|
contentContainerStyle={{ padding: 16, paddingBottom: 32 }}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
<Text style={LABEL_STYLE}>Title *</Text>
|
|
<TextInput
|
|
style={FIELD_STYLE}
|
|
value={form.title}
|
|
onChangeText={set("title")}
|
|
placeholder="e.g. Ghost blog down after k3s node drain"
|
|
placeholderTextColor={Colors.textDim}
|
|
returnKeyType="next"
|
|
/>
|
|
|
|
<Text style={LABEL_STYLE}>Service</Text>
|
|
<TextInput
|
|
style={FIELD_STYLE}
|
|
value={form.service}
|
|
onChangeText={set("service")}
|
|
placeholder="e.g. ghost-blog, k3s-cluster, haproxy"
|
|
placeholderTextColor={Colors.textDim}
|
|
autoCapitalize="none"
|
|
returnKeyType="next"
|
|
/>
|
|
|
|
<Text style={LABEL_STYLE}>Symptom</Text>
|
|
<TextInput
|
|
style={MONO_FIELD_STYLE}
|
|
value={form.symptom}
|
|
onChangeText={set("symptom")}
|
|
placeholder="What was observed?"
|
|
placeholderTextColor={Colors.textDim}
|
|
multiline
|
|
returnKeyType="next"
|
|
/>
|
|
|
|
<Text style={LABEL_STYLE}>Root Cause</Text>
|
|
<TextInput
|
|
style={MONO_FIELD_STYLE}
|
|
value={form.rootCause}
|
|
onChangeText={set("rootCause")}
|
|
placeholder="Why did it happen?"
|
|
placeholderTextColor={Colors.textDim}
|
|
multiline
|
|
returnKeyType="next"
|
|
/>
|
|
|
|
<Text style={LABEL_STYLE}>Fix Applied</Text>
|
|
<TextInput
|
|
style={MONO_FIELD_STYLE}
|
|
value={form.fix}
|
|
onChangeText={set("fix")}
|
|
placeholder="What did you do to fix it?"
|
|
placeholderTextColor={Colors.textDim}
|
|
multiline
|
|
returnKeyType="done"
|
|
/>
|
|
|
|
<Text style={LABEL_STYLE}>Tags</Text>
|
|
<TextInput
|
|
style={FIELD_STYLE}
|
|
value={tagInput}
|
|
onChangeText={setTagInput}
|
|
onSubmitEditing={handleTagInputEnd}
|
|
onBlur={handleTagInputEnd}
|
|
placeholder="Type tag and press Enter"
|
|
placeholderTextColor={Colors.textDim}
|
|
autoCapitalize="none"
|
|
returnKeyType="done"
|
|
blurOnSubmit={false}
|
|
/>
|
|
{form.tags.length > 0 && (
|
|
<View
|
|
style={{ flexDirection: "row", flexWrap: "wrap", gap: 8, marginBottom: 16 }}
|
|
>
|
|
{form.tags.map((tag) => (
|
|
<Pressable
|
|
key={tag}
|
|
onPress={() => removeTag(tag)}
|
|
style={{
|
|
backgroundColor: Colors.surface2,
|
|
borderRadius: 4,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
}}
|
|
>
|
|
<Text style={{ color: Colors.text2, fontSize: 13 }}>{tag}</Text>
|
|
<Text style={{ color: Colors.textDim, fontSize: 12 }}>✕</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
)}
|
|
|
|
<Pressable
|
|
onPress={handleSave}
|
|
disabled={saving}
|
|
style={{
|
|
backgroundColor: saving ? Colors.primaryDim : Colors.primary,
|
|
borderRadius: 10,
|
|
padding: 16,
|
|
alignItems: "center",
|
|
marginTop: 8,
|
|
}}
|
|
>
|
|
<Text
|
|
style={{ color: "#fff", fontSize: 16, fontWeight: "700" }}
|
|
>
|
|
{saving ? "Saving…" : "Save Incident"}
|
|
</Text>
|
|
</Pressable>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
}
|