Yaltopia-Tickets-App/app/customers/[id].tsx
2026-06-17 15:16:40 +03:00

334 lines
13 KiB
TypeScript

import React, { useState, useCallback } from "react";
import {
View,
ScrollView,
ActivityIndicator,
Pressable,
Modal,
} from "react-native";
import { useSirouRouter } from "@sirou/react-native";
import { AppRoutes } from "@/lib/routes";
import { Stack, useLocalSearchParams, useFocusEffect } from "expo-router";
import { Text } from "@/components/ui/text";
import {
User,
Building2,
Mail,
Phone,
MapPin,
Hash,
Tag,
ShieldCheck,
BookOpen,
Pencil,
Trash2,
} 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 CustomerDetailScreen() {
const nav = useSirouRouter<AppRoutes>();
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [deleting, setDeleting] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const fetch = useCallback(async () => {
try {
setLoading(true);
const cId = Array.isArray(id) ? id[0] : id;
if (!cId) return;
const response = await api.customers.getById({ params: { id: cId } });
setData(response || null);
} catch {
toast.error("Error", "Failed to load customer");
setData(null);
} finally {
setLoading(false);
}
}, [id]);
useFocusEffect(useCallback(() => { fetch(); }, [fetch]));
const handleDelete = async () => {
try {
setDeleting(true);
const cId = Array.isArray(id) ? id[0] : id;
await api.customers.delete({ params: { id: cId } });
toast.success("Deleted", "Customer has been deleted");
setShowDeleteModal(false);
nav.back();
} catch (err: any) {
toast.error("Error", err?.message || "Failed to delete customer");
} finally {
setDeleting(false);
}
};
if (loading) {
return (
<ScreenWrapper className="bg-background">
<StandardHeader title="Customer" showBack />
<View className="flex-1 items-center justify-center">
<ActivityIndicator size="large" color="#E46212" />
</View>
</ScreenWrapper>
);
}
if (!data) {
return (
<ScreenWrapper className="bg-background">
<StandardHeader title="Customer" showBack />
<View className="flex-1 items-center justify-center px-8">
<Text className="text-muted-foreground text-center font-sans-medium">
Failed to load customer details.
</Text>
<Pressable onPress={fetch} className="mt-4 px-6 py-2 bg-primary rounded-[6px]">
<Text className="text-white font-sans-bold text-sm">Retry</Text>
</Pressable>
</View>
</ScreenWrapper>
);
}
const isCompany = data?.type === "COMPANY";
const d = data || {};
return (
<ScreenWrapper className="bg-background">
<Stack.Screen options={{ headerShown: false }} />
<StandardHeader title="Customer" showBack />
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 120 }}
showsVerticalScrollIndicator={false}
>
{/* Identity Header */}
<View className="px-5 pt-6 pb-2">
<View className="flex-row items-center gap-4 mb-4">
<View className={`h-14 w-14 rounded-full items-center justify-center ${isCompany ? "bg-blue-500/10" : "bg-primary/10"}`}>
{isCompany ? (
<Building2 color="#2563eb" size={26} strokeWidth={2} />
) : (
<User color="#E46212" size={26} strokeWidth={2} />
)}
</View>
<View className="flex-1">
<View className="flex-row items-center gap-2">
<Text className="text-xl font-sans-black text-foreground tracking-tight flex-1">
{d.displayName || "—"}
</Text>
<View className={`px-2.5 py-1 rounded-[4px] ${isCompany ? "bg-blue-500/10" : "bg-primary/10"}`}>
<Text className={`text-[9px] font-sans-bold uppercase tracking-widest ${isCompany ? "text-blue-600" : "text-primary"}`}>
{isCompany ? "Company" : "Individual"}
</Text>
</View>
</View>
<Text className="text-muted-foreground text-xs font-sans-medium mt-0.5">
Created {d.createdAt ? new Date(d.createdAt).toLocaleDateString() : "—"}
</Text>
</View>
</View>
</View>
{/* Contact */}
{(d.email || d.phone) && (
<View className="px-5 mb-5">
<Text className="text-[10px] font-sans-bold uppercase tracking-wider text-muted-foreground mb-2">
Contact
</Text>
<View className="bg-card rounded-[6px] border border-border p-4 gap-3">
<InfoRow icon={<Mail color="#E46212" size={14} strokeWidth={2} />} label="Email" value={d.email} />
{d.email && d.phone ? <View className="border-b border-border/40" /> : null}
<InfoRow icon={<Phone color="#E46212" size={14} strokeWidth={2} />} label="Phone" value={d.phone} />
</View>
</View>
)}
{/* Identity Details */}
{(d.firstName || d.lastName || d.companyName) && (
<View className="px-5 mb-5">
<Text className="text-[10px] font-sans-bold uppercase tracking-wider text-muted-foreground mb-2">
Identity Details
</Text>
<View className="bg-card rounded-[6px] border border-border p-4 gap-3">
{isCompany ? (
<InfoRow icon={<Building2 color="#2563eb" size={14} strokeWidth={2} />} label="Company Name" value={d.companyName} />
) : (
<>
<InfoRow icon={<User color="#E46212" size={14} strokeWidth={2} />} label="First Name" value={d.firstName} />
{d.firstName && d.lastName ? <View className="border-b border-border/40" /> : null}
<InfoRow icon={<User color="#E46212" size={14} strokeWidth={2} />} label="Last Name" value={d.lastName} />
</>
)}
</View>
</View>
)}
{/* Documents */}
{(d.tin || d.vatRegistrationNumber || d.businessLicenseNumber) && (
<View className="px-5 mb-5">
<Text className="text-[10px] font-sans-bold uppercase tracking-wider text-muted-foreground mb-2">
Documents
</Text>
<View className="bg-card rounded-[6px] border border-border p-4 gap-3">
<InfoRow icon={<Hash color="#E46212" size={14} strokeWidth={2} />} label="TIN Number" value={d.tin} />
{d.tin && d.vatRegistrationNumber ? <View className="border-b border-border/40" /> : null}
<InfoRow icon={<Tag color="#E46212" size={14} strokeWidth={2} />} label="VAT Registration" value={d.vatRegistrationNumber} />
{(d.tin || d.vatRegistrationNumber) && d.businessLicenseNumber ? <View className="border-b border-border/40" /> : null}
<InfoRow icon={<ShieldCheck color="#E46212" size={14} strokeWidth={2} />} label="Business License" value={d.businessLicenseNumber} />
</View>
</View>
)}
{/* Address */}
{d.address && (
<View className="px-5 mb-5">
<Text className="text-[10px] font-sans-bold uppercase tracking-wider text-muted-foreground mb-2">
Address
</Text>
<View className="bg-card rounded-[6px] border border-border p-4">
<View className="flex-row items-start gap-3">
<View className="w-8 h-8 rounded-full bg-primary/10 items-center justify-center mt-0.5">
<MapPin color="#E46212" size={14} strokeWidth={2} />
</View>
<Text className="text-foreground font-sans-medium text-sm leading-5 flex-1">
{d.address}
</Text>
</View>
</View>
</View>
)}
{/* Notes */}
{d.notes && (
<View className="px-5 mb-5">
<Text className="text-[10px] font-sans-bold uppercase tracking-wider text-muted-foreground mb-2">
Notes
</Text>
<View className="bg-card rounded-[6px] border border-border p-4">
<View className="flex-row items-start gap-3">
<View className="w-8 h-8 rounded-full bg-primary/10 items-center justify-center mt-0.5">
<BookOpen color="#E46212" size={14} strokeWidth={2} />
</View>
<Text className="text-foreground font-sans-medium text-sm leading-5 flex-1">
{d.notes}
</Text>
</View>
</View>
</View>
)}
{/* Divider */}
<View className="mx-5 mb-5 border-t border-border/60" />
{/* Action Buttons */}
<View className="px-5 mb-6 gap-3">
<Pressable
onPress={() => {
const cId = Array.isArray(id) ? id[0] : id;
nav.go("customers/edit", { id: cId });
}}
className="bg-primary h-11 rounded-[6px] flex-row items-center justify-center gap-2"
>
<Pencil color="white" size={16} strokeWidth={2.5} />
<Text className="text-white font-sans-bold text-[11px] uppercase tracking-widest">
Edit Customer
</Text>
</Pressable>
<Pressable
onPress={() => setShowDeleteModal(true)}
className="bg-red-500 h-11 rounded-[6px] flex-row items-center justify-center gap-2"
>
<Trash2 color="white" size={16} strokeWidth={2.5} />
<Text className="text-white font-sans-bold text-[11px] uppercase tracking-widest">
Delete Customer
</Text>
</Pressable>
</View>
</ScrollView>
{/* Delete Confirmation Modal */}
<Modal
visible={showDeleteModal}
transparent
animationType="fade"
onRequestClose={() => setShowDeleteModal(false)}
>
<Pressable
className="flex-1 bg-black/50 items-center justify-center px-8"
onPress={() => setShowDeleteModal(false)}
>
<Pressable
className="bg-card rounded-2xl p-6 w-full border border-border"
onPress={(e) => e.stopPropagation()}
>
<View className="items-center mb-5">
<View className="w-14 h-14 rounded-full bg-red-500/10 items-center justify-center mb-4">
<Trash2 color="#EF4444" size={24} strokeWidth={2} />
</View>
<Text className="text-[18px] font-sans-bold text-foreground text-center">
Delete Customer?
</Text>
<Text className="text-muted-foreground text-sm font-sans-medium text-center mt-2 leading-5">
This will permanently delete{" "}
<Text className="font-sans-bold text-foreground">
{d.displayName}
</Text>{" "}
and all associated data. This action cannot be undone.
</Text>
</View>
<View className="gap-3">
<Pressable
onPress={handleDelete}
disabled={deleting}
className="bg-red-500 h-12 rounded-[6px] items-center justify-center"
>
{deleting ? (
<ActivityIndicator color="#ffffff" size="small" />
) : (
<Text className="text-white font-sans-bold text-sm">
Yes, Delete
</Text>
)}
</Pressable>
<Pressable
onPress={() => setShowDeleteModal(false)}
className="bg-secondary h-12 rounded-[6px] items-center justify-center border border-border"
>
<Text className="text-foreground font-sans-bold text-sm">
Cancel
</Text>
</Pressable>
</View>
</Pressable>
</Pressable>
</Modal>
</ScreenWrapper>
);
}
function InfoRow({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) {
if (!value) return null;
return (
<View className="flex-row items-center gap-3">
<View className="w-8 h-8 rounded-full bg-primary/10 items-center justify-center">
{icon}
</View>
<View className="flex-1">
<Text className="text-[10px] font-sans-bold uppercase tracking-wider text-muted-foreground">
{label}
</Text>
<Text className="text-foreground font-sans-bold text-sm mt-px">{value}</Text>
</View>
</View>
);
}