693 lines
22 KiB
TypeScript
693 lines
22 KiB
TypeScript
import React, { useEffect, useState, useMemo } from "react";
|
|
import {
|
|
View,
|
|
ScrollView,
|
|
Pressable,
|
|
TextInput,
|
|
StyleSheet,
|
|
ActivityIndicator,
|
|
Platform,
|
|
PermissionsAndroid,
|
|
} from "react-native";
|
|
import { useColorScheme } from "nativewind";
|
|
import { Text } from "@/components/ui/text";
|
|
import { ChevronDown, CalendarSearch, Search, Link2 } from "@/lib/icons";
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { useSirouRouter } from "@sirou/react-native";
|
|
import { AppRoutes } from "@/lib/routes";
|
|
import { api } from "@/lib/api";
|
|
import { toast } from "@/lib/toast-store";
|
|
import { EmptyState } from "@/components/EmptyState";
|
|
import { PickerModal, SelectOption } from "@/components/PickerModal";
|
|
import { CalendarGrid } from "@/components/CalendarGrid";
|
|
import { FormFlow } from "@/components/FormFlow";
|
|
import { CustomerPicker } from "@/components/CustomerPicker";
|
|
import { getPlaceholderColor } from "@/lib/colors";
|
|
|
|
let SmsAndroid: any = null;
|
|
if (Platform.OS === "android") {
|
|
try {
|
|
const smsModule = require("react-native-get-sms-android");
|
|
SmsAndroid = smsModule.default || smsModule;
|
|
} catch (e) {
|
|
console.log("[CreatePayment] SMS module unavailable");
|
|
}
|
|
}
|
|
|
|
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,
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
onChangeText: (v: string) => void;
|
|
placeholder: string;
|
|
numeric?: boolean;
|
|
flex?: number;
|
|
multiline?: boolean;
|
|
}) {
|
|
const c = useInputColors();
|
|
return (
|
|
<View style={flex != null ? { flex } : undefined}>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
{label}
|
|
</Text>
|
|
<TextInput
|
|
style={[
|
|
S.input,
|
|
{ backgroundColor: c.bg, borderColor: c.border, color: c.text },
|
|
multiline
|
|
? { height: 80, paddingTop: 10, textAlignVertical: "top" }
|
|
: {},
|
|
]}
|
|
placeholder={placeholder}
|
|
placeholderTextColor={c.placeholder}
|
|
value={value}
|
|
onChangeText={onChangeText}
|
|
keyboardType={numeric ? "numeric" : "default"}
|
|
multiline={multiline}
|
|
autoCorrect={false}
|
|
autoCapitalize="none"
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function PickerField({
|
|
label,
|
|
value,
|
|
onPress,
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
onPress: () => void;
|
|
}) {
|
|
const c = useInputColors();
|
|
return (
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
{label}
|
|
</Text>
|
|
<Pressable
|
|
onPress={onPress}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
<Text className="text-xs font-sans-bold" style={{ color: c.text }}>
|
|
{value}
|
|
</Text>
|
|
<ChevronDown size={14} color={c.text} strokeWidth={3} />
|
|
</Pressable>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const CURRENCIES = ["ETB", "USD", "EUR", "GBP", "KES", "ZAR"];
|
|
const PAYMENT_METHODS = [
|
|
"Telebirr",
|
|
"CBE",
|
|
"Dashen",
|
|
"DECSI",
|
|
"Bank Transfer",
|
|
"Cash",
|
|
"Other",
|
|
];
|
|
|
|
function parseSmsMessage(body: string) {
|
|
const text = body.toUpperCase();
|
|
let bank = "";
|
|
let ref = "";
|
|
let amount = "";
|
|
|
|
if (text.includes("CBE")) {
|
|
bank = "CBE";
|
|
const dirMatch = body.match(/(debited|credited)\s+with\s+ETB([\d,.]+)/i);
|
|
if (dirMatch) amount = dirMatch[2];
|
|
const refMatch = body.match(/id=(\w+)/i);
|
|
if (refMatch) ref = refMatch[1];
|
|
} else if (text.includes("TELEBIRR")) {
|
|
bank = "Telebirr";
|
|
const dirMatch = body.match(/(received|sent|paid)\s+ETB\s*([\d,.]+)/i);
|
|
if (dirMatch) amount = dirMatch[2];
|
|
const refMatch = body.match(/transaction number\s+(\w+)/i);
|
|
if (refMatch) ref = refMatch[1];
|
|
}
|
|
|
|
return { bank, ref, amount };
|
|
}
|
|
|
|
export default function CreatePaymentScreen() {
|
|
const nav = useSirouRouter<AppRoutes>();
|
|
const [step, setStep] = useState(0);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
const [transactionId, setTransactionId] = useState("");
|
|
const [amount, setAmount] = useState("");
|
|
const [currency, setCurrency] = useState("ETB");
|
|
const [paymentMethod, setPaymentMethod] = useState("Telebirr");
|
|
const [paymentDate, setPaymentDate] = useState(
|
|
new Date().toISOString().split("T")[0],
|
|
);
|
|
const [notes, setNotes] = useState("");
|
|
const [selectedInvoice, setSelectedInvoice] = useState<any>(null);
|
|
|
|
const [customerName, setCustomerName] = useState("");
|
|
const [customerEmail, setCustomerEmail] = useState("");
|
|
const [customerPhone, setCustomerPhone] = useState("");
|
|
|
|
const [showCurrency, setShowCurrency] = useState(false);
|
|
const [showPaymentMethod, setShowPaymentMethod] = useState(false);
|
|
const [showPaymentDate, setShowPaymentDate] = useState(false);
|
|
const [showInvoicePicker, setShowInvoicePicker] = useState(false);
|
|
|
|
const [invoices, setInvoices] = useState<any[]>([]);
|
|
const [invoiceSearch, setInvoiceSearch] = useState("");
|
|
const [loadingInvoices, setLoadingInvoices] = useState(false);
|
|
|
|
const { colorScheme } = useColorScheme();
|
|
const isDark = colorScheme === "dark";
|
|
const c = useInputColors();
|
|
|
|
useEffect(() => {
|
|
if (Platform.OS !== "android" || !SmsAndroid) return;
|
|
|
|
(async () => {
|
|
try {
|
|
const granted = await PermissionsAndroid.request(
|
|
PermissionsAndroid.PERMISSIONS.READ_SMS,
|
|
);
|
|
if (granted !== PermissionsAndroid.RESULTS.GRANTED) return;
|
|
|
|
const fiveMinsAgo = Date.now() - 5 * 60 * 1000;
|
|
const filter = {
|
|
box: "inbox",
|
|
minDate: fiveMinsAgo,
|
|
maxCount: 30,
|
|
};
|
|
|
|
SmsAndroid.list(
|
|
JSON.stringify(filter),
|
|
() => {},
|
|
(_count: number, smsList: string) => {
|
|
const messages = JSON.parse(smsList);
|
|
const match = messages.find((m: any) => {
|
|
const addr = (m.address || "").toUpperCase();
|
|
const body = (m.body || "").toUpperCase();
|
|
return (
|
|
addr === "127" ||
|
|
addr === "CBE" ||
|
|
body.includes("TELEBIRR") ||
|
|
body.includes("CBE")
|
|
);
|
|
});
|
|
|
|
if (!match) return;
|
|
|
|
const {
|
|
bank,
|
|
ref,
|
|
amount: parsedAmount,
|
|
} = parseSmsMessage(match.body);
|
|
if (ref || parsedAmount) {
|
|
setTransactionId(ref || match.body.slice(0, 20));
|
|
if (parsedAmount) setAmount(parsedAmount.replace(/,/g, ""));
|
|
if (bank) setPaymentMethod(bank);
|
|
}
|
|
},
|
|
);
|
|
} catch {
|
|
// silent
|
|
}
|
|
})();
|
|
}, []);
|
|
|
|
const openInvoicePicker = async () => {
|
|
setLoadingInvoices(true);
|
|
try {
|
|
const response = await api.invoices.getAll({ query: { limit: 50 } });
|
|
const list = Array.isArray(response)
|
|
? response
|
|
: (response as any).data || [];
|
|
setInvoices(list);
|
|
setInvoiceSearch("");
|
|
setShowInvoicePicker(true);
|
|
} catch (err: any) {
|
|
toast.error("Error", "Failed to fetch invoices.");
|
|
} finally {
|
|
setLoadingInvoices(false);
|
|
}
|
|
};
|
|
|
|
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 STEPS = [
|
|
{ key: "details", label: "Payment Details" },
|
|
{ key: "invoice", label: "Invoice" },
|
|
{ key: "info", label: "Customer Info" },
|
|
{ key: "summary", label: "Summary" },
|
|
];
|
|
|
|
const handleNext = () => {
|
|
if (step === 0) {
|
|
if (!transactionId.trim()) {
|
|
toast.error("Validation Error", "Transaction ID is required");
|
|
return;
|
|
}
|
|
if (!amount || parseFloat(amount) <= 0) {
|
|
toast.error(
|
|
"Validation Error",
|
|
"Amount is required and must be greater than 0",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
setStep(step + 1);
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!transactionId.trim()) {
|
|
toast.error("Validation Error", "Transaction ID is required");
|
|
return;
|
|
}
|
|
if (!amount || parseFloat(amount) <= 0) {
|
|
toast.error(
|
|
"Validation Error",
|
|
"Amount is required and must be greater than 0",
|
|
);
|
|
return;
|
|
}
|
|
|
|
setSubmitting(true);
|
|
try {
|
|
const payload = {
|
|
transactionId: transactionId.trim(),
|
|
amount: parseFloat(amount),
|
|
currency,
|
|
paymentDate: new Date(paymentDate).toISOString(),
|
|
paymentMethod,
|
|
notes,
|
|
customerName: customerName.trim() || undefined,
|
|
customerEmail: customerEmail.trim() || undefined,
|
|
customerPhone: customerPhone.trim() ? `+251${customerPhone.trim()}` : undefined,
|
|
...(selectedInvoice?.id ? { invoiceId: selectedInvoice.id } : {}),
|
|
};
|
|
|
|
await api.payments.create({ body: payload });
|
|
toast.success("Success", "Payment created successfully!");
|
|
nav.back();
|
|
} catch (error: any) {
|
|
console.error("[CreatePayment] Error:", error);
|
|
const msg =
|
|
error?.response?.data?.message ||
|
|
error?.data?.message ||
|
|
error?.message ||
|
|
"Failed to create payment";
|
|
toast.error("Error", msg);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<FormFlow
|
|
steps={STEPS}
|
|
currentStep={step}
|
|
onNext={handleNext}
|
|
onBack={() => setStep(step - 1)}
|
|
onComplete={handleSubmit}
|
|
loading={submitting}
|
|
completeLabel="Create Payment"
|
|
>
|
|
{step === 0 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Payment Details
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<Field
|
|
label="Transaction ID"
|
|
value={transactionId}
|
|
onChangeText={setTransactionId}
|
|
placeholder="e.g. TXN-2024-001"
|
|
/>
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="Amount"
|
|
value={amount}
|
|
onChangeText={setAmount}
|
|
placeholder="0.00"
|
|
numeric
|
|
flex={1}
|
|
/>
|
|
<PickerField
|
|
label="Currency"
|
|
value={currency}
|
|
onPress={() => setShowCurrency(true)}
|
|
/>
|
|
</View>
|
|
<PickerField
|
|
label="Payment Method"
|
|
value={paymentMethod}
|
|
onPress={() => setShowPaymentMethod(true)}
|
|
/>
|
|
<PickerField
|
|
label="Payment Date"
|
|
value={paymentDate}
|
|
onPress={() => setShowPaymentDate(true)}
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 1 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Invoice
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<View>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Link Invoice
|
|
</Text>
|
|
<Pressable
|
|
onPress={openInvoicePicker}
|
|
disabled={loadingInvoices}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
{loadingInvoices ? (
|
|
<ActivityIndicator color="#ea580c" size="small" />
|
|
) : (
|
|
<>
|
|
<Text
|
|
className="text-xs font-sans-medium flex-1"
|
|
style={{
|
|
color: selectedInvoice ? c.text : c.placeholder,
|
|
}}
|
|
numberOfLines={1}
|
|
>
|
|
{selectedInvoice
|
|
? `#${selectedInvoice.invoiceNumber || selectedInvoice.id} — ${selectedInvoice.customerName || ""}`
|
|
: "Select an invoice (optional)"}
|
|
</Text>
|
|
<Link2 size={14} color="#ea580c" strokeWidth={2.5} />
|
|
</>
|
|
)}
|
|
</Pressable>
|
|
</View>
|
|
<Field
|
|
label="Notes"
|
|
value={notes}
|
|
onChangeText={setNotes}
|
|
placeholder="e.g. Payment for invoice INV-2024-001"
|
|
multiline
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Customer Info
|
|
</Text>
|
|
<View className="gap-4">
|
|
<View>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Customer Name
|
|
</Text>
|
|
<CustomerPicker
|
|
value={customerName}
|
|
onSelect={(c) => {
|
|
setCustomerName(c.name);
|
|
setCustomerEmail(c.email);
|
|
setCustomerPhone(c.phone.replace("+251", ""));
|
|
}}
|
|
placeholder="Select or search for a customer"
|
|
/>
|
|
</View>
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="Email"
|
|
value={customerEmail}
|
|
onChangeText={setCustomerEmail}
|
|
placeholder="billing@acme.com"
|
|
flex={1}
|
|
/>
|
|
</View>
|
|
<View>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Phone
|
|
</Text>
|
|
<View className="flex-row items-center h-11 px-3 border border-border rounded-[6px]" style={{ backgroundColor: c.bg, borderColor: c.border }}>
|
|
<Text className="text-foreground font-sans-bold text-xs">+251</Text>
|
|
<TextInput
|
|
className="flex-1 ml-2 text-foreground text-xs font-sans-medium"
|
|
placeholder="912345678"
|
|
placeholderTextColor={c.placeholder}
|
|
value={customerPhone}
|
|
onChangeText={setCustomerPhone}
|
|
keyboardType="phone-pad"
|
|
maxLength={9}
|
|
style={{ textAlignVertical: "center" }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Summary
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] p-4 border border-border gap-3">
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Transaction ID
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{transactionId}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Payment Method
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{paymentMethod}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Payment Date
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{paymentDate}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Linked Invoice
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{selectedInvoice
|
|
? `#${selectedInvoice.invoiceNumber || selectedInvoice.id}`
|
|
: "None"}
|
|
</Text>
|
|
</View>
|
|
{notes ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Notes
|
|
</Text>
|
|
<Text
|
|
className="text-[14px] text-foreground font-sans-bold flex-1 text-right ml-4"
|
|
numberOfLines={2}
|
|
>
|
|
{notes}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
<View className="border-t border-border/40 my-1" />
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[16px] font-sans-bold text-foreground">
|
|
Total Amount
|
|
</Text>
|
|
<Text className="text-[16px] font-sans-bold text-primary">
|
|
{currency}{" "}
|
|
{(parseFloat(amount) || 0).toLocaleString("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
})}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</FormFlow>
|
|
|
|
<PickerModal
|
|
visible={showCurrency}
|
|
onClose={() => setShowCurrency(false)}
|
|
title="Select Currency"
|
|
>
|
|
{CURRENCIES.map((curr) => (
|
|
<SelectOption
|
|
key={curr}
|
|
label={curr}
|
|
value={curr}
|
|
selected={currency === curr}
|
|
onSelect={(v) => {
|
|
setCurrency(v);
|
|
setShowCurrency(false);
|
|
}}
|
|
/>
|
|
))}
|
|
</PickerModal>
|
|
|
|
<PickerModal
|
|
visible={showPaymentMethod}
|
|
onClose={() => setShowPaymentMethod(false)}
|
|
title="Select Payment Method"
|
|
>
|
|
{PAYMENT_METHODS.map((method) => (
|
|
<SelectOption
|
|
key={method}
|
|
label={method}
|
|
value={method}
|
|
selected={paymentMethod === method}
|
|
onSelect={(v) => {
|
|
setPaymentMethod(v);
|
|
setShowPaymentMethod(false);
|
|
}}
|
|
/>
|
|
))}
|
|
</PickerModal>
|
|
|
|
<PickerModal
|
|
visible={showPaymentDate}
|
|
onClose={() => setShowPaymentDate(false)}
|
|
title="Select Payment Date"
|
|
>
|
|
<CalendarGrid
|
|
selectedDate={paymentDate}
|
|
onSelect={(v) => {
|
|
setPaymentDate(v);
|
|
setShowPaymentDate(false);
|
|
}}
|
|
/>
|
|
</PickerModal>
|
|
|
|
<PickerModal
|
|
visible={showInvoicePicker}
|
|
onClose={() => setShowInvoicePicker(false)}
|
|
title="Link Invoice (Optional)"
|
|
>
|
|
<View className="px-4 pb-3">
|
|
<View className="flex-row items-center rounded-xl px-3 border border-border h-10">
|
|
<Search size={16} color={isDark ? "#94a3b8" : "#64748b"} />
|
|
<TextInput
|
|
className="flex-1 ml-2 text-foreground py-0 text-sm"
|
|
placeholder="Search by number or customer..."
|
|
placeholderTextColor={getPlaceholderColor(isDark)}
|
|
value={invoiceSearch}
|
|
onChangeText={setInvoiceSearch}
|
|
autoCorrect={false}
|
|
style={{ textAlignVertical: "center" }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
{loadingInvoices ? (
|
|
<View className="py-10 items-center">
|
|
<ActivityIndicator color="#ea580c" />
|
|
</View>
|
|
) : (
|
|
<ScrollView className="max-h-96" keyboardShouldPersistTaps="handled">
|
|
<Pressable
|
|
onPress={() => {
|
|
setSelectedInvoice(null);
|
|
setShowInvoicePicker(false);
|
|
}}
|
|
className="px-4 py-3 border-b border-border/40 flex-row items-center"
|
|
>
|
|
<Text className="text-muted-foreground text-sm font-sans-medium">
|
|
None — skip linking
|
|
</Text>
|
|
</Pressable>
|
|
{filteredInvoices.length > 0 ? (
|
|
filteredInvoices.map((inv) => (
|
|
<Pressable
|
|
key={inv.id}
|
|
onPress={() => {
|
|
setSelectedInvoice(inv);
|
|
setShowInvoicePicker(false);
|
|
}}
|
|
className="px-4 py-3 border-b border-border/40 flex-row items-center"
|
|
>
|
|
<View className="flex-1">
|
|
<Text className="text-foreground font-sans-bold text-sm">
|
|
{inv.customerName || "Unknown"}
|
|
</Text>
|
|
<Text className="text-muted-foreground text-[10px] font-sans-semibold mt-0.5">
|
|
#{inv.invoiceNumber || inv.id} · {inv.currency || "ETB"}{" "}
|
|
{Number(inv.amount || 0).toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<Link2 size={16} color="#ea580c" strokeWidth={2.5} />
|
|
</Pressable>
|
|
))
|
|
) : (
|
|
<EmptyState
|
|
title={
|
|
invoiceSearch
|
|
? "No invoices match your search"
|
|
: "No invoices available"
|
|
}
|
|
/>
|
|
)}
|
|
</ScrollView>
|
|
)}
|
|
</PickerModal>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|