297 lines
10 KiB
TypeScript
297 lines
10 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
||
import { View, ScrollView, Pressable, ActivityIndicator } from "react-native";
|
||
import { useSirouRouter } from "@sirou/react-native";
|
||
import { AppRoutes } from "@/lib/routes";
|
||
import { Stack, useLocalSearchParams } from "expo-router";
|
||
import { Text } from "@/components/ui/text";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Card } from "@/components/ui/card";
|
||
import {
|
||
ArrowLeft,
|
||
DraftingCompass,
|
||
Clock,
|
||
Send,
|
||
ExternalLink,
|
||
ChevronRight,
|
||
CheckCircle2,
|
||
} from "@/lib/icons";
|
||
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
||
import { StandardHeader } from "@/components/StandardHeader";
|
||
import { api } from "@/lib/api";
|
||
import { toast } from "@/lib/toast-store";
|
||
|
||
export default function ProformaDetailScreen() {
|
||
const nav = useSirouRouter<AppRoutes>();
|
||
const { id } = useLocalSearchParams();
|
||
|
||
const [loading, setLoading] = useState(true);
|
||
const [proforma, setProforma] = useState<any>(null);
|
||
|
||
useEffect(() => {
|
||
fetchProforma();
|
||
}, [id]);
|
||
|
||
const fetchProforma = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const data = await api.proforma.getById({ params: { id: id as string } });
|
||
setProforma(data);
|
||
} catch (error: any) {
|
||
console.error("[ProformaDetail] Error:", error);
|
||
toast.error("Error", "Failed to load proforma details");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<ScreenWrapper className="bg-background">
|
||
<Stack.Screen options={{ headerShown: false }} />
|
||
<StandardHeader title="Proforma" showBack />
|
||
<View className="flex-1 justify-center items-center">
|
||
<ActivityIndicator color="#ea580c" size="large" />
|
||
</View>
|
||
</ScreenWrapper>
|
||
);
|
||
}
|
||
|
||
if (!proforma) {
|
||
return (
|
||
<ScreenWrapper className="bg-background">
|
||
<Stack.Screen options={{ headerShown: false }} />
|
||
<StandardHeader title="Proforma" showBack />
|
||
<View className="flex-1 justify-center items-center">
|
||
<Text variant="muted">Proforma not found</Text>
|
||
</View>
|
||
</ScreenWrapper>
|
||
);
|
||
}
|
||
|
||
const subtotal =
|
||
proforma.items?.reduce(
|
||
(acc: number, item: any) => acc + (Number(item.total) || 0),
|
||
0,
|
||
) || 0;
|
||
|
||
return (
|
||
<ScreenWrapper className="bg-background">
|
||
<Stack.Screen options={{ headerShown: false }} />
|
||
|
||
{/* Header */}
|
||
<StandardHeader title="Proforma" showBack />
|
||
|
||
<ScrollView
|
||
className="flex-1"
|
||
contentContainerStyle={{ padding: 16, paddingBottom: 120 }}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
{/* Blue Summary Card */}
|
||
<Card className="overflow-hidden rounded-[6px] border-0 bg-primary mb-4">
|
||
<View className="p-5">
|
||
<View className="flex-row items-center justify-between mb-3">
|
||
<View className="bg-white/20 p-1.5 rounded-[6px]">
|
||
<DraftingCompass color="white" size={16} strokeWidth={2.5} />
|
||
</View>
|
||
<View className="bg-amber-500/20 px-3 py-1 rounded-[6px] border border-white/10">
|
||
<Text className="text-[10px] font-bold text-white uppercase tracking-widest">
|
||
ACTIVE
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<Text variant="small" className="text-white/70 mb-0.5">
|
||
Customer: {proforma.customerName}
|
||
</Text>
|
||
<Text variant="h3" className="text-white font-bold mb-3">
|
||
{proforma.description || "Proforma Request"}
|
||
</Text>
|
||
|
||
<View className="flex-row items-center gap-3 border-t border-white/40 pt-3">
|
||
<View className="flex-row items-center gap-1.5">
|
||
<Clock color="rgba(255,255,255,0.9)" size={12} />
|
||
<Text className="text-white/90 text-xs font-semibold">
|
||
Due {new Date(proforma.dueDate).toLocaleDateString()}
|
||
</Text>
|
||
</View>
|
||
<View className="h-3 w-[1px] bg-white/60" />
|
||
<Text className="text-white/90 text-xs font-semibold">
|
||
{proforma.proformaNumber}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</Card>
|
||
|
||
{/* Customer Info Strip (Added for functionality while keeping style) */}
|
||
<Card className="bg-card rounded-[6px] mb-4">
|
||
<View className="flex-row px-4 py-2">
|
||
<View className="flex-1 flex-row items-center">
|
||
<View className="flex-col">
|
||
<Text className="text-foreground text-[10px] opacity-60 uppercase font-bold">
|
||
Email
|
||
</Text>
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-semibold text-xs"
|
||
numberOfLines={1}
|
||
>
|
||
{proforma.customerEmail || "N/A"}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
<View className="w-[1px] bg-border/70 mx-3" />
|
||
<View className="flex-1 flex-row items-center">
|
||
<View className="flex-col">
|
||
<Text className="text-foreground text-[10px] opacity-60 uppercase font-bold">
|
||
Phone
|
||
</Text>
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-semibold text-xs"
|
||
numberOfLines={1}
|
||
>
|
||
{proforma.customerPhone || "N/A"}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</Card>
|
||
|
||
{/* Line Items Card */}
|
||
<Card className="bg-card rounded-[6px] mb-4">
|
||
<View className="p-4">
|
||
<View className="flex-row items-center gap-2 mb-2">
|
||
<Text
|
||
variant="small"
|
||
className="font-bold uppercase tracking-widest text-[10px] opacity-60"
|
||
>
|
||
Line Items
|
||
</Text>
|
||
</View>
|
||
|
||
{proforma.items?.map((item: any, i: number) => (
|
||
<View
|
||
key={item.id || i}
|
||
className={`flex-row justify-between py-3 ${i < proforma.items.length - 1 ? "border-b border-border/40" : ""}`}
|
||
>
|
||
<View className="flex-1 pr-4">
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-semibold text-sm"
|
||
>
|
||
{item.description}
|
||
</Text>
|
||
<Text variant="muted" className="text-[10px] mt-0.5">
|
||
{item.quantity} × {proforma.currency}{" "}
|
||
{Number(item.unitPrice).toLocaleString()}
|
||
</Text>
|
||
</View>
|
||
<Text variant="p" className="text-foreground font-bold text-sm">
|
||
{proforma.currency} {Number(item.total).toLocaleString()}
|
||
</Text>
|
||
</View>
|
||
))}
|
||
|
||
<View className="mt-3 pt-3 border-t border-border/40 gap-2">
|
||
<View className="flex-row justify-between">
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-semibold text-sm"
|
||
>
|
||
Subtotal
|
||
</Text>
|
||
<Text variant="p" className="text-foreground font-bold text-sm">
|
||
{proforma.currency} {subtotal.toLocaleString()}
|
||
</Text>
|
||
</View>
|
||
{Number(proforma.taxAmount) > 0 && (
|
||
<View className="flex-row justify-between">
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-semibold text-sm"
|
||
>
|
||
Tax
|
||
</Text>
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-bold text-sm"
|
||
>
|
||
{proforma.currency}{" "}
|
||
{Number(proforma.taxAmount).toLocaleString()}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
{Number(proforma.discountAmount) > 0 && (
|
||
<View className="flex-row justify-between">
|
||
<Text
|
||
variant="p"
|
||
className="text-red-500 font-semibold text-sm"
|
||
>
|
||
Discount
|
||
</Text>
|
||
<Text variant="p" className="text-red-500 font-bold text-sm">
|
||
-{proforma.currency}{" "}
|
||
{Number(proforma.discountAmount).toLocaleString()}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
<View className="flex-row justify-between items-center mt-1">
|
||
<Text variant="p" className="text-foreground font-bold">
|
||
Total Amount
|
||
</Text>
|
||
<Text
|
||
variant="h4"
|
||
className="text-foreground font-bold tracking-tight"
|
||
>
|
||
{proforma.currency} {Number(proforma.amount).toLocaleString()}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</Card>
|
||
|
||
{/* Notes Section (New) */}
|
||
{proforma.notes && (
|
||
<Card className="bg-card rounded-[6px] mb-4">
|
||
<View className="p-4">
|
||
<Text
|
||
variant="small"
|
||
className="font-bold uppercase tracking-widest text-[10px] opacity-60 mb-2"
|
||
>
|
||
Additional Notes
|
||
</Text>
|
||
<Text
|
||
variant="p"
|
||
className="text-foreground font-medium text-xs leading-5"
|
||
>
|
||
{proforma.notes}
|
||
</Text>
|
||
</View>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Actions */}
|
||
<View className="flex-row gap-3">
|
||
<Button
|
||
className="flex-1 h-11 rounded-[6px] bg-primary"
|
||
onPress={() => {}}
|
||
>
|
||
<Send color="#ffffff" size={14} strokeWidth={2.5} />
|
||
<Text className="ml-2 text-white font-bold text-[11px] uppercase tracking-widest">
|
||
Share
|
||
</Text>
|
||
</Button>
|
||
<Button
|
||
className="flex-1 h-11 rounded-[6px] bg-card border border-border"
|
||
onPress={() => nav.back()}
|
||
>
|
||
<Text className="text-foreground font-semibold text-[11px] uppercase tracking-widest">
|
||
Back
|
||
</Text>
|
||
</Button>
|
||
</View>
|
||
</ScrollView>
|
||
</ScreenWrapper>
|
||
);
|
||
}
|