import React, { useState, useEffect, useMemo } from "react";
import {
View,
Pressable,
TextInput,
StyleSheet,
ActivityIndicator,
ScrollView,
Modal,
Dimensions,
} from "react-native";
import { useColorScheme } from "nativewind";
import * as DocumentPicker from "expo-document-picker";
import { Text } from "@/components/ui/text";
import {
ChevronDown,
Upload,
X,
FileText,
Plus,
Link2,
Search,
} from "@/lib/icons";
import { ScreenWrapper } from "@/components/ScreenWrapper";
import { useSirouRouter } from "@sirou/react-native";
import { useLocalSearchParams } from "expo-router";
import { AppRoutes } from "@/lib/routes";
import { api, BASE_URL } from "@/lib/api";
import { toast } from "@/lib/toast-store";
import { useAuthStore } from "@/lib/auth-store";
import { PickerModal, SelectOption } from "@/components/PickerModal";
import { FormFlow } from "@/components/FormFlow";
import { getPlaceholderColor } from "@/lib/colors";
const { height: SCREEN_HEIGHT } = Dimensions.get("window");
type DeclarationType = "VAT" | "WITHHOLDING_TAX";
type Period = "MONTHLY" | "QUARTERLY" | "SEMI_ANNUAL" | "ANNUAL" | "ONE_TIME" | "";
type FileEntry = { uri: string; name: string; type: string };
const DECLARATION_TYPES: SelectOption[] = [
{ label: "VAT", value: "VAT" },
{ label: "Withholding Tax", value: "WITHHOLDING_TAX" },
];
const PERIODS: SelectOption[] = [
{ label: "Monthly", value: "MONTHLY" },
{ label: "Quarterly", value: "QUARTERLY" },
{ label: "Semi-Annual", value: "SEMI_ANNUAL" },
{ label: "Annual", value: "ANNUAL" },
{ label: "One-Time", value: "ONE_TIME" },
];
const S = StyleSheet.create({
input: {
paddingHorizontal: 12,
paddingVertical: 12,
fontSize: 12,
fontWeight: "500",
borderRadius: 6,
borderWidth: 1,
textAlignVertical: "center",
},
});
function useInputColors() {
const { colorScheme } = useColorScheme();
const dark = colorScheme === "dark";
return {
bg: dark ? "rgba(30,30,30,0.8)" : "rgba(241,245,249,0.2)",
border: dark ? "rgba(255,255,255,0.08)" : "rgba(203,213,225,0.6)",
text: dark ? "#f1f5f9" : "#0f172a",
placeholder: "rgba(100,116,139,0.45)",
};
}
function Field({
label,
value,
onChangeText,
placeholder,
numeric = false,
flex,
multiline = false,
required = false,
}: {
label: string;
value: string;
onChangeText: (v: string) => void;
placeholder: string;
numeric?: boolean;
flex?: number;
multiline?: boolean;
required?: boolean;
}) {
const c = useInputColors();
return (
{label}
{required && *}
);
}
function PickerField({
label,
value,
onPress,
required = false,
}: {
label: string;
value: string;
onPress: () => void;
required?: boolean;
}) {
const c = useInputColors();
return (
{label}
{required && *}
{value}
);
}
export default function EditDeclarationScreen() {
const nav = useSirouRouter();
const { id } = useLocalSearchParams<{ id: string }>();
const { colorScheme } = useColorScheme();
const isDark = colorScheme === "dark";
const c = useInputColors();
const [initialLoading, setInitialLoading] = useState(true);
const [step, setStep] = useState(0);
const [submitting, setSubmitting] = useState(false);
const [declarationNumber, setDeclarationNumber] = useState("");
const [type, setType] = useState("VAT");
const [title, setTitle] = useState("");
const [period, setPeriod] = useState("");
const [periodStart, setPeriodStart] = useState("");
const [periodEnd, setPeriodEnd] = useState("");
const [tin, setTin] = useState("");
const [taxAccountNumber, setTaxAccountNumber] = useState("");
const [taxCentre, setTaxCentre] = useState("");
const [selectedInvoices, setSelectedInvoices] = useState([]);
const [declarationFile, setDeclarationFile] = useState(null);
const [receiptFiles, setReceiptFiles] = useState([]);
const [suggestedFilename, setSuggestedFilename] = useState("");
const [notes, setNotes] = useState("");
const [dueDate, setDueDate] = useState("");
const [showTypePicker, setShowTypePicker] = useState(false);
const [showPeriodPicker, setShowPeriodPicker] = useState(false);
const [showInvoicePicker, setShowInvoicePicker] = useState(false);
const [invoices, setInvoices] = useState([]);
const [invoiceSearch, setInvoiceSearch] = useState("");
const [loadingInvoices, setLoadingInvoices] = useState(false);
useEffect(() => {
const load = async () => {
try {
const res = await api.declarations.getById({ params: { id } });
const d = res?.data ?? res;
if (!d) throw new Error("Not found");
setDeclarationNumber(d.declarationNumber || "");
setType(d.type || "VAT");
setTitle(d.title || "");
setPeriod(d.period || "");
setPeriodStart(
d.periodStart
? new Date(d.periodStart).toISOString().split("T")[0]
: "",
);
setPeriodEnd(
d.periodEnd
? new Date(d.periodEnd).toISOString().split("T")[0]
: "",
);
setTin(d.tin || "");
setTaxAccountNumber(d.taxAccountNumber || "");
setTaxCentre(d.taxCentre || "");
setSelectedInvoices(d.invoices || []);
setNotes(d.notes || "");
setDueDate(
d.dueDate
? new Date(d.dueDate).toISOString().split("T")[0]
: "",
);
setSuggestedFilename(d.suggestedFilename || "");
} catch (err: any) {
toast.error("Error", "Failed to load declaration");
nav.back();
} finally {
setInitialLoading(false);
}
};
load();
}, [id]);
const steps = [
{ key: "details", label: "Declaration Detail" },
{ key: "tax", label: "Tax Info" },
{ key: "files", label: "Files" },
{ key: "notes", label: "Notes" },
{ key: "review", label: "Review" },
];
const openInvoicePicker = async () => {
setLoadingInvoices(true);
try {
const response = await api.invoices.getAll({ query: { limit: 50 } });
setInvoices(Array.isArray(response) ? response : response.data || []);
setInvoiceSearch("");
setShowInvoicePicker(true);
} catch {
toast.error("Error", "Failed to fetch invoices");
} finally {
setLoadingInvoices(false);
}
};
const toggleInvoice = (inv: any) => {
setSelectedInvoices((prev) =>
prev.find((i) => i.id === inv.id)
? prev.filter((i) => i.id !== inv.id)
: [...prev, inv],
);
};
const filteredInvoices = useMemo(() => {
if (!invoiceSearch) return invoices;
const q = invoiceSearch.toLowerCase();
return invoices.filter(
(inv) =>
(inv.invoiceNumber || "").toLowerCase().includes(q) ||
(inv.customerName || "").toLowerCase().includes(q),
);
}, [invoices, invoiceSearch]);
const pickFile = async () => {
try {
const result = await DocumentPicker.getDocumentAsync({
type: "application/pdf",
copyToCacheDirectory: true,
});
if (!result.canceled && result.assets?.length > 0) {
const asset = result.assets[0];
setDeclarationFile({
uri: asset.uri,
name: asset.name,
type: asset.mimeType || "application/pdf",
});
}
} catch {
toast.warning("Picker", "Could not open document picker");
}
};
const pickReceipts = async () => {
try {
const result = await DocumentPicker.getDocumentAsync({
type: "*/*",
copyToCacheDirectory: true,
multiple: true,
});
if (!result.canceled && result.assets?.length > 0) {
setReceiptFiles((prev) => [
...prev,
...result.assets.map((a: any) => ({
uri: a.uri,
name: a.name,
type: a.mimeType || "application/octet-stream",
})),
]);
}
} catch {
toast.warning("Picker", "Could not open document picker");
}
};
const handleNext = () => {
if (step === 0) {
if (!declarationNumber.trim()) {
toast.error("Validation", "Declaration number is required");
return;
}
if (!title.trim()) {
toast.error("Validation", "Title is required");
return;
}
if (!periodStart.trim()) {
toast.error("Validation", "Period start is required");
return;
}
if (!periodEnd.trim()) {
toast.error("Validation", "Period end is required");
return;
}
}
setStep(step + 1);
};
const handleSubmit = async () => {
setSubmitting(true);
try {
const payload: Record = {
declarationNumber: declarationNumber.trim(),
type,
period,
title: title.trim(),
periodStart: new Date(periodStart).toISOString(),
periodEnd: new Date(periodEnd).toISOString(),
};
if (tin.trim()) payload.tin = tin.trim();
if (taxAccountNumber.trim()) payload.taxAccountNumber = taxAccountNumber.trim();
if (taxCentre.trim()) payload.taxCentre = taxCentre.trim();
if (notes.trim()) payload.notes = notes.trim();
if (dueDate.trim()) payload.dueDate = new Date(dueDate).toISOString();
if (suggestedFilename.trim()) payload.suggestedFilename = suggestedFilename.trim();
if (selectedInvoices.length > 0) {
payload.invoiceIds = selectedInvoices.map((i) => i.id);
}
const hasFiles = declarationFile !== null || receiptFiles.length > 0;
if (hasFiles) {
const { accessToken } = useAuthStore.getState();
const formData = new FormData();
formData.append("data", JSON.stringify(payload));
if (declarationFile) {
formData.append("file", {
uri: declarationFile.uri,
name: declarationFile.name,
type: declarationFile.type,
} as any);
}
receiptFiles.forEach((rf) => {
formData.append("receipts", {
uri: rf.uri,
name: rf.name,
type: rf.type,
} as any);
});
const response = await fetch(`${BASE_URL}declarations/${id}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "multipart/form-data",
},
body: formData,
});
if (!response.ok) throw new Error(await response.text());
} else {
await api.declarations.update({ params: { id }, body: payload });
}
toast.success("Success", "Declaration updated");
nav.back();
} catch (error: any) {
toast.error(
"Error",
error?.response?.data?.message ||
error?.data?.message ||
error?.message ||
"Failed to update declaration",
);
} finally {
setSubmitting(false);
}
};
if (initialLoading) {
return (
);
}
return (
setStep(step - 1)}
onComplete={handleSubmit}
loading={submitting}
completeLabel="Update Declaration"
>
{step === 0 && (
Declaration Detail
setShowTypePicker(true)}
/>
setShowPeriodPicker(true)}
/>
)}
{step === 1 && (
Tax Info
Link Invoices
{loadingInvoices ? (
) : (
<>
0
? c.text
: c.placeholder,
}}
numberOfLines={1}
>
{selectedInvoices.length > 0
? `${selectedInvoices.length} invoice${selectedInvoices.length > 1 ? "s" : ""} linked`
: "Select invoices to link (optional)"}
>
)}
)}
{step === 2 && (
File Uploads
MoR Declaration PDF
{declarationFile ? (
{declarationFile.name}
setDeclarationFile(null)}>
) : (
Tap to select PDF
)}
Payment Receipts
{receiptFiles.length > 0 && (
{receiptFiles.map((rf, idx) => (
{rf.name}
setReceiptFiles((p) =>
p.filter((_, i) => i !== idx),
)
}
>
))}
)}
Add Receipt
)}
{step === 3 && (
)}
{step === 4 && (
Summary
{dueDate ? : null}
{tin ? : null}
{taxAccountNumber ? (
) : null}
{taxCentre ? (
) : null}
{selectedInvoices.length > 0 ? (
1 ? "s" : ""}`}
/>
) : null}
{notes && }
{declarationFile && (
)}
{receiptFiles.length > 0 && (
Receipts ({receiptFiles.length})
{receiptFiles.map((rf, i) => (
{i + 1}. {rf.name}
))}
)}
)}
setShowInvoicePicker(false)}
>
setShowInvoicePicker(false)}
>
e.stopPropagation()}
>
Link Invoices
setShowInvoicePicker(false)}
className="h-8 w-8 bg-secondary/80 rounded-full items-center justify-center border border-border/10"
>
✕
{filteredInvoices.map((inv: any) => {
const selected = selectedInvoices.find((i) => i.id === inv.id);
return (
toggleInvoice(inv)}
className={`flex-row items-center py-3 px-3 rounded-[8px] mb-1 ${
selected ? "bg-primary/10" : ""
}`}
>
{inv.customerName || "Unknown"}
#{inv.invoiceNumber} · {inv.currency}{" "}
{Number(inv.amount?.value || inv.amount || 0).toLocaleString()}
{selected && (
✓
)}
);
})}
{filteredInvoices.length === 0 && (
No invoices found
)}
setShowTypePicker(false)}
>
{DECLARATION_TYPES.map((opt) => (
{
setType(v as DeclarationType);
setShowTypePicker(false);
}}
/>
))}
setShowPeriodPicker(false)}
>
{PERIODS.map((opt) => (
{
setPeriod(v as Period);
setShowPeriodPicker(false);
}}
/>
))}
);
}
function SummaryRow({ label, value }: { label: string; value: string }) {
return (
{label}
{value}
);
}