821 lines
28 KiB
TypeScript
821 lines
28 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { View, Pressable, TextInput, StyleSheet, Platform } from "react-native";
|
|
import { Text } from "@/components/ui/text";
|
|
import { Trash2, Plus, ChevronDown, Info, Check } from "@/lib/icons";
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { useSirouRouter } from "@sirou/react-native";
|
|
import { AppRoutes } from "@/lib/routes";
|
|
import { useColorScheme } from "nativewind";
|
|
import { api } from "@/lib/api";
|
|
import { toast } from "@/lib/toast-store";
|
|
import { PickerModal, SelectOption } from "@/components/PickerModal";
|
|
import { CalendarGrid } from "@/components/CalendarGrid";
|
|
import { FormFlow } from "@/components/FormFlow";
|
|
import { CustomerPicker } from "@/components/CustomerPicker";
|
|
|
|
type Item = { id: number; description: string; qty: string; price: string };
|
|
|
|
const S = StyleSheet.create({
|
|
input: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 12,
|
|
fontSize: 12,
|
|
fontWeight: "500",
|
|
borderRadius: 6,
|
|
borderWidth: 1,
|
|
textAlignVertical: "center",
|
|
},
|
|
inputCenter: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 10,
|
|
fontSize: 14,
|
|
fontWeight: "500",
|
|
borderRadius: 6,
|
|
borderWidth: 1,
|
|
textAlign: "center",
|
|
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,
|
|
center = false,
|
|
flex,
|
|
multiline = false,
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
onChangeText: (v: string) => void;
|
|
placeholder: string;
|
|
numeric?: boolean;
|
|
center?: 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={[
|
|
center ? S.inputCenter : 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"
|
|
returnKeyType="next"
|
|
/>
|
|
</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 = ["USD", "ETB", "EUR", "GBP", "KES", "ZAR"];
|
|
|
|
const STEPS = [
|
|
{ key: "customer", label: "Customer" },
|
|
{ key: "details", label: "Details" },
|
|
{ key: "items", label: "Items" },
|
|
{ key: "tax", label: "Tax" },
|
|
{ key: "notes", label: "Notes" },
|
|
{ key: "summary", label: "Summary" },
|
|
];
|
|
|
|
export default function CreateProformaScreen() {
|
|
const nav = useSirouRouter<AppRoutes>();
|
|
const [loading, setLoading] = useState(false);
|
|
const [step, setStep] = useState(0);
|
|
|
|
// Fields
|
|
const [proformaNumber, setProformaNumber] = useState("");
|
|
const [customerName, setCustomerName] = useState("");
|
|
const [customerEmail, setCustomerEmail] = useState("");
|
|
const [customerPhone, setCustomerPhone] = useState("");
|
|
const [description, setDescription] = useState("");
|
|
const [currency, setCurrency] = useState("ETB");
|
|
const [taxAmount, setTaxAmount] = useState("0");
|
|
const [discountAmount, setDiscountAmount] = useState("0");
|
|
const [notes, setNotes] = useState("");
|
|
const [sendEmail, setSendEmail] = useState(false);
|
|
const [sendSms, setSendSms] = useState(false);
|
|
const [showSendOptions, setShowSendOptions] = useState(false);
|
|
const [tooltipItemId, setTooltipItemId] = useState<number | null>(null);
|
|
|
|
// Dates
|
|
const [issueDate, setIssueDate] = useState(
|
|
new Date().toISOString().split("T")[0],
|
|
);
|
|
const [dueDate, setDueDate] = useState("");
|
|
|
|
const [items, setItems] = useState<Item[]>([
|
|
{ id: 1, description: "", qty: "1", price: "" },
|
|
]);
|
|
|
|
const c = useInputColors();
|
|
|
|
// Modal States
|
|
const [showCurrency, setShowCurrency] = useState(false);
|
|
const [showIssueDate, setShowIssueDate] = useState(false);
|
|
const [showDueDate, setShowDueDate] = useState(false);
|
|
|
|
// Auto-generate Proforma Number and set default dates on mount
|
|
useEffect(() => {
|
|
const year = new Date().getFullYear();
|
|
const random = Math.floor(1000 + Math.random() * 9000);
|
|
setProformaNumber(`PROF-${year}-${random}`);
|
|
|
|
// Default Due Date: 30 days from now
|
|
const d = new Date();
|
|
d.setDate(d.getDate() + 30);
|
|
setDueDate(d.toISOString().split("T")[0]);
|
|
}, []);
|
|
|
|
const updateField = (id: number, field: keyof Item, value: string) =>
|
|
setItems((prev) =>
|
|
prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)),
|
|
);
|
|
|
|
const addItem = () =>
|
|
setItems((prev) => [
|
|
...prev,
|
|
{ id: Date.now(), description: "", qty: "1", price: "" },
|
|
]);
|
|
|
|
const removeItem = (id: number) => {
|
|
if (items.length > 1)
|
|
setItems((prev) => prev.filter((item) => item.id !== id));
|
|
};
|
|
|
|
const subtotal = items.reduce(
|
|
(sum, item) =>
|
|
sum + (parseFloat(item.qty) || 0) * (parseFloat(item.price) || 0),
|
|
0,
|
|
);
|
|
|
|
const total =
|
|
subtotal + (parseFloat(taxAmount) || 0) - (parseFloat(discountAmount) || 0);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!customerName) {
|
|
toast.error("Validation Error", "Please enter a customer name");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setLoading(true);
|
|
|
|
const formattedPhone = customerPhone ? `+251${customerPhone}` : "";
|
|
|
|
const payload = {
|
|
proformaNumber,
|
|
customerName,
|
|
customerEmail,
|
|
customerPhone: formattedPhone,
|
|
amount: Number(total.toFixed(2)),
|
|
currency,
|
|
issueDate: new Date(issueDate).toISOString(),
|
|
dueDate: new Date(dueDate).toISOString(),
|
|
description: description || `Proforma for ${customerName}`,
|
|
notes,
|
|
taxAmount: parseFloat(taxAmount) || 0,
|
|
discountAmount: parseFloat(discountAmount) || 0,
|
|
sendEmail,
|
|
sendSms,
|
|
items: items.map((i) => ({
|
|
description: i.description || "Item",
|
|
quantity: parseFloat(i.qty) || 0,
|
|
unitPrice: parseFloat(i.price) || 0,
|
|
total: Number(
|
|
((parseFloat(i.qty) || 0) * (parseFloat(i.price) || 0)).toFixed(2),
|
|
),
|
|
})),
|
|
};
|
|
|
|
await api.proforma.create({ body: payload });
|
|
toast.success("Success", "Proforma created successfully!");
|
|
nav.back();
|
|
} catch (err: any) {
|
|
console.error("[ProformaCreate] Error:", err);
|
|
toast.error("Error", err.message || "Failed to create proforma");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleNext = () => {
|
|
switch (step) {
|
|
case 0:
|
|
if (!customerName.trim()) {
|
|
toast.error("Validation Error", "Please enter a customer name");
|
|
return;
|
|
}
|
|
break;
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
if (items.length === 0) {
|
|
toast.error("Validation Error", "Please add at least one item");
|
|
return;
|
|
}
|
|
break;
|
|
case 3:
|
|
break;
|
|
case 4:
|
|
break;
|
|
}
|
|
setStep((s) => s + 1);
|
|
};
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<FormFlow
|
|
steps={STEPS}
|
|
currentStep={step}
|
|
onNext={handleNext}
|
|
onBack={() => setStep((s) => s - 1)}
|
|
onComplete={handleSubmit}
|
|
loading={loading}
|
|
>
|
|
{step === 0 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Customer Information
|
|
</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>
|
|
<Field
|
|
label="Email"
|
|
value={customerEmail}
|
|
onChangeText={setCustomerEmail}
|
|
placeholder="billing@acme.com"
|
|
/>
|
|
<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 === 1 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
General Information
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<Field
|
|
label="Proforma Number"
|
|
value={proformaNumber}
|
|
onChangeText={setProformaNumber}
|
|
placeholder="e.g. PROF-2024-001"
|
|
/>
|
|
<Field
|
|
label="Project Description"
|
|
value={description}
|
|
onChangeText={setDescription}
|
|
placeholder="e.g. Web Development Services"
|
|
/>
|
|
</View>
|
|
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Schedule & Currency
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<View className="flex-row gap-4">
|
|
<PickerField
|
|
label="Issue Date"
|
|
value={issueDate}
|
|
onPress={() => setShowIssueDate(true)}
|
|
/>
|
|
<PickerField
|
|
label="Due Date"
|
|
value={dueDate || "Select"}
|
|
onPress={() => setShowDueDate(true)}
|
|
/>
|
|
</View>
|
|
<PickerField
|
|
label="Currency"
|
|
value={currency}
|
|
onPress={() => setShowCurrency(true)}
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<View className="gap-5">
|
|
<View className="flex-row items-center justify-between">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Billable Items
|
|
</Text>
|
|
<Pressable
|
|
onPress={addItem}
|
|
className="flex-row items-center gap-1 px-3 py-1.5 rounded-[6px] bg-primary/10 border border-primary/20"
|
|
>
|
|
<Plus color="#ea580c" size={14} strokeWidth={2.5} />
|
|
<Text className="text-primary text-[10px] font-sans-bold">
|
|
Add
|
|
</Text>
|
|
</Pressable>
|
|
</View>
|
|
<View className="gap-3">
|
|
{items.map((item, index) => (
|
|
<View
|
|
key={item.id}
|
|
className={`bg-card pb-4 ${index < items.length - 1 ? "border-b border-border" : ""}`}
|
|
>
|
|
<View className="flex-row justify-between items-center mb-3">
|
|
<Text className="text-[16px] font-sans-bold text-foreground">
|
|
Item {index + 1}
|
|
</Text>
|
|
{items.length > 1 && (
|
|
<Pressable
|
|
onPress={() => removeItem(item.id)}
|
|
hitSlop={8}
|
|
>
|
|
<Trash2 color="#ef4444" size={16} />
|
|
</Pressable>
|
|
)}
|
|
</View>
|
|
<Field
|
|
label="Description"
|
|
placeholder="e.g. UI Design"
|
|
value={item.description}
|
|
onChangeText={(v) => updateField(item.id, "description", v)}
|
|
/>
|
|
<View className="flex-row gap-3 mt-4">
|
|
<Field
|
|
label="Qty"
|
|
placeholder="1"
|
|
numeric
|
|
center
|
|
value={item.qty}
|
|
onChangeText={(v) => updateField(item.id, "qty", v)}
|
|
flex={1}
|
|
/>
|
|
<View className="flex-[3]">
|
|
<View className="flex-row items-center gap-1.5 mb-1.5 ml-1">
|
|
<Text className="text-[14px] font-sans-bold text-foreground">
|
|
Price
|
|
</Text>
|
|
<Pressable
|
|
onPress={() =>
|
|
setTooltipItemId(
|
|
tooltipItemId === item.id ? null : item.id,
|
|
)
|
|
}
|
|
className="h-2 w-2 rounded-full items-center justify-center"
|
|
>
|
|
<Info size={14} color="#94a3b8" strokeWidth={2.5} />
|
|
</Pressable>
|
|
</View>
|
|
<View className="relative overflow-visible">
|
|
<TextInput
|
|
style={[
|
|
S.input,
|
|
{
|
|
backgroundColor: c.bg,
|
|
borderColor: c.border,
|
|
color: c.text,
|
|
},
|
|
]}
|
|
placeholder="0.00"
|
|
placeholderTextColor={c.placeholder}
|
|
value={item.price}
|
|
onChangeText={(v) => updateField(item.id, "price", v)}
|
|
keyboardType="numeric"
|
|
autoCorrect={false}
|
|
autoCapitalize="none"
|
|
returnKeyType="next"
|
|
/>
|
|
{tooltipItemId === item.id && (
|
|
<View
|
|
className="absolute rounded-[4px] px-2 py-1 border border-primary/20"
|
|
style={{
|
|
top: -4,
|
|
left: 20,
|
|
backgroundColor: "#fef2f2",
|
|
elevation: 8,
|
|
zIndex: 999,
|
|
shadowColor: "#000",
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 3,
|
|
shadowOffset: { width: 0, height: 1 },
|
|
}}
|
|
>
|
|
<Text className="text-[9px] font-sans-bold tracking-wide text-primary">
|
|
before tax
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Tax & Discount
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="Tax"
|
|
value={taxAmount}
|
|
onChangeText={setTaxAmount}
|
|
placeholder="0"
|
|
numeric
|
|
flex={1}
|
|
/>
|
|
<Field
|
|
label="Discount"
|
|
value={discountAmount}
|
|
onChangeText={setDiscountAmount}
|
|
placeholder="0"
|
|
numeric
|
|
flex={1}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="bg-card rounded-[6px] gap-2">
|
|
<View className="flex-row justify-between items-center">
|
|
<Text className="text-[14px] text-muted-foreground font-sans-medium">
|
|
Subtotal
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{currency}{" "}
|
|
{subtotal.toLocaleString("en-US", {
|
|
minimumFractionDigits: 2,
|
|
})}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between items-center">
|
|
<Text className="text-[14px] text-muted-foreground font-sans-medium">
|
|
Tax
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
+{currency}{" "}
|
|
{(parseFloat(taxAmount) || 0).toLocaleString("en-US", {
|
|
minimumFractionDigits: 2,
|
|
})}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between items-center">
|
|
<Text className="text-[14px] text-muted-foreground font-sans-medium">
|
|
Discount
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
-{currency}{" "}
|
|
{(parseFloat(discountAmount) || 0).toLocaleString("en-US", {
|
|
minimumFractionDigits: 2,
|
|
})}
|
|
</Text>
|
|
</View>
|
|
<View className="border-t border-border my-1" />
|
|
<View className="flex-row justify-between items-center">
|
|
<Text className="text-[16px] font-sans-bold text-foreground">
|
|
Total
|
|
</Text>
|
|
<Text className="text-[18px] font-sans-bold text-primary">
|
|
{currency}{" "}
|
|
{total.toLocaleString("en-US", { minimumFractionDigits: 2 })}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 4 && (
|
|
<View className="gap-5">
|
|
<View className="bg-card rounded-[6px]">
|
|
<Field
|
|
label="Notes"
|
|
value={notes}
|
|
onChangeText={setNotes}
|
|
placeholder="e.g. Payment due within 30 days"
|
|
multiline
|
|
/>
|
|
</View>
|
|
|
|
<View className="bg-card rounded-[6px] border border-border overflow-hidden">
|
|
<Pressable
|
|
onPress={() => setShowSendOptions(true)}
|
|
className="flex-row items-center justify-between p-4"
|
|
>
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold text-foreground">
|
|
Send automatically
|
|
</Text>
|
|
<Text className="text-[11px] text-muted-foreground font-sans-medium mt-0.5">
|
|
Email & SMS notifications
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row items-center gap-2">
|
|
{(sendEmail || sendSms) && (
|
|
<View className="flex-row gap-1">
|
|
{sendEmail && (
|
|
<View className="px-2 py-0.5 rounded-[4px] bg-primary/10">
|
|
<Text className="text-[9px] font-sans-bold uppercase tracking-widest text-primary">
|
|
Email
|
|
</Text>
|
|
</View>
|
|
)}
|
|
{sendSms && (
|
|
<View className="px-2 py-0.5 rounded-[4px] bg-primary/10">
|
|
<Text className="text-[9px] font-sans-bold uppercase tracking-widest text-primary">
|
|
SMS
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
<ChevronDown size={16} color="#64748b" strokeWidth={2.5} />
|
|
</View>
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 5 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Summary
|
|
</Text>
|
|
|
|
<View className="bg-card rounded-[6px] gap-3">
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Customer
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-bold">
|
|
{customerName}
|
|
</Text>
|
|
</View>
|
|
{(customerEmail || customerPhone) && (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Contact
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-medium">
|
|
{customerEmail ||
|
|
(customerPhone ? `+251${customerPhone}` : "")}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
<View className="border-t border-border/40 my-1" />
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Proforma #
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-bold">
|
|
{proformaNumber}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Items
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-medium">
|
|
{items.filter((i) => i.description.trim()).length} items
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Description
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-medium">
|
|
{description || "N/A"}
|
|
</Text>
|
|
</View>
|
|
{notes ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Notes
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-medium max-w-[55%] text-right">
|
|
{notes}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-xs text-muted-foreground font-sans-medium">
|
|
Currency
|
|
</Text>
|
|
<Text className="text-xs text-foreground font-sans-bold">
|
|
{currency}
|
|
</Text>
|
|
</View>
|
|
<View className="border-t border-border/40 my-1" />
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-sm font-sans-bold text-foreground">
|
|
Total
|
|
</Text>
|
|
<Text className="text-sm font-sans-bold text-primary">
|
|
{currency}{" "}
|
|
{total.toLocaleString("en-US", {
|
|
minimumFractionDigits: 2,
|
|
})}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</FormFlow>
|
|
|
|
{/* Currency Modal */}
|
|
<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>
|
|
|
|
{/* Issue Date Modal */}
|
|
<PickerModal
|
|
visible={showIssueDate}
|
|
onClose={() => setShowIssueDate(false)}
|
|
title="Select Issue Date"
|
|
>
|
|
<CalendarGrid
|
|
selectedDate={issueDate}
|
|
onSelect={(v) => {
|
|
setIssueDate(v);
|
|
setShowIssueDate(false);
|
|
}}
|
|
/>
|
|
</PickerModal>
|
|
|
|
{/* Due Date Modal */}
|
|
<PickerModal
|
|
visible={showDueDate}
|
|
onClose={() => setShowDueDate(false)}
|
|
title="Select Due Date"
|
|
>
|
|
<CalendarGrid
|
|
selectedDate={dueDate}
|
|
onSelect={(v) => {
|
|
setDueDate(v);
|
|
setShowDueDate(false);
|
|
}}
|
|
/>
|
|
</PickerModal>
|
|
|
|
{/* Send Notification Options Modal */}
|
|
<PickerModal
|
|
visible={showSendOptions}
|
|
onClose={() => setShowSendOptions(false)}
|
|
title="Send automatically"
|
|
>
|
|
<View className="mb-3">
|
|
<Text className="text-[12px] text-muted-foreground font-sans-medium leading-[16px]">
|
|
Choose how you want to notify the customer when this proforma is
|
|
created.
|
|
</Text>
|
|
</View>
|
|
<Pressable
|
|
onPress={() => setSendEmail(!sendEmail)}
|
|
className={`flex-row items-center justify-between p-4 mb-3 rounded-[6px] border ${
|
|
sendEmail
|
|
? "bg-primary/5 border-primary/20"
|
|
: "bg-secondary/20 border-border/5"
|
|
}`}
|
|
>
|
|
<Text
|
|
className={`font-sans-bold text-[14px] ${sendEmail ? "text-primary" : "text-foreground"}`}
|
|
>
|
|
Email notification
|
|
</Text>
|
|
<View
|
|
className={`h-5 w-5 rounded-[4px] items-center justify-center ${sendEmail ? "bg-primary" : "border border-border"}`}
|
|
>
|
|
{sendEmail && <Check size={12} color="white" strokeWidth={4} />}
|
|
</View>
|
|
</Pressable>
|
|
<Pressable
|
|
onPress={() => setSendSms(!sendSms)}
|
|
className={`flex-row items-center justify-between p-4 mb-3 rounded-[6px] border ${
|
|
sendSms
|
|
? "bg-primary/5 border-primary/20"
|
|
: "bg-secondary/20 border-border/5"
|
|
}`}
|
|
>
|
|
<Text
|
|
className={`font-sans-bold text-[14px] ${sendSms ? "text-primary" : "text-foreground"}`}
|
|
>
|
|
SMS notification
|
|
</Text>
|
|
<View
|
|
className={`h-5 w-5 rounded-[4px] items-center justify-center ${sendSms ? "bg-primary" : "border border-border"}`}
|
|
>
|
|
{sendSms && <Check size={12} color="white" strokeWidth={4} />}
|
|
</View>
|
|
</Pressable>
|
|
</PickerModal>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|