Yaltopia-Tickets-App/components/CustomerPicker.tsx
2026-06-05 13:39:37 +03:00

200 lines
7.8 KiB
TypeScript

import React, { useState, useMemo } from "react";
import {
View,
Pressable,
Modal,
ScrollView,
ActivityIndicator,
TextInput,
Dimensions,
} from "react-native";
import { useSirouRouter } from "@sirou/react-native";
import { Text } from "@/components/ui/text";
import { Search, X, Plus, User, Building2, ChevronDown } from "@/lib/icons";
import { api } from "@/lib/api";
import { useColorScheme } from "nativewind";
const { height: SCREEN_HEIGHT } = Dimensions.get("window");
interface CustomerData {
name: string;
email: string;
phone: string;
}
interface CustomerPickerProps {
value: string;
onSelect: (c: CustomerData) => void;
placeholder?: string;
}
export function CustomerPicker({ value, onSelect, placeholder }: CustomerPickerProps) {
const nav = useSirouRouter<any>();
const { colorScheme } = useColorScheme();
const isDark = colorScheme === "dark";
const [show, setShow] = useState(false);
const [customers, setCustomers] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState("");
const openPicker = async () => {
setShow(true);
setSearch("");
setLoading(true);
try {
const res = await api.customers.getAll({ query: { page: 1, limit: 50 } });
setCustomers(res?.data || []);
} catch {
setCustomers([]);
} finally {
setLoading(false);
}
};
const filtered = useMemo(() => {
if (!search.trim()) return customers;
const q = search.toLowerCase();
return customers.filter(
(c: any) =>
(c.displayName || "")?.toLowerCase().includes(q) ||
(c.firstName || "")?.toLowerCase().includes(q) ||
(c.lastName || "")?.toLowerCase().includes(q) ||
(c.email || "")?.toLowerCase().includes(q) ||
(c.phone || "")?.toLowerCase().includes(q),
);
}, [customers, search]);
return (
<>
<Pressable
onPress={openPicker}
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between bg-card"
>
<Text className={`text-xs font-sans-medium ${value ? "text-foreground" : "text-muted-foreground"}`}>
{value || placeholder || "Select a customer"}
</Text>
<ChevronDown size={14} color="#94a3b8" strokeWidth={2.5} />
</Pressable>
<Modal
visible={show}
transparent
animationType="slide"
onRequestClose={() => setShow(false)}
>
<Pressable className="flex-1 bg-black/40" onPress={() => setShow(false)}>
<View className="flex-1 justify-end">
<Pressable
className="bg-card rounded-t-[36px] overflow-hidden border-t-[3px] border-border/20"
style={{ maxHeight: SCREEN_HEIGHT * 0.8 }}
onPress={(e) => e.stopPropagation()}
>
<View className="px-6 pb-4 pt-4 flex-row justify-between items-center">
<View className="w-10" />
<Text className="text-foreground font-sans-bold text-[18px]">Customers</Text>
<Pressable
onPress={() => setShow(false)}
className="h-8 w-8 bg-secondary/80 rounded-full items-center justify-center border border-border/10"
>
<X size={14} color={isDark ? "#f1f5f9" : "#0f172a"} strokeWidth={2.5} />
</Pressable>
</View>
<View className="px-5 pb-4">
<View className="bg-background rounded-[6px] border border-border flex-row items-center px-3.5 py-2.5">
<Search size={15} color="#94a3b8" strokeWidth={2} />
<TextInput
className="flex-1 ml-2.5 text-foreground font-sans-medium text-sm"
placeholder="Search customers..."
placeholderTextColor="#94a3b8"
value={search}
onChangeText={setSearch}
/>
{search.length > 0 && (
<Pressable onPress={() => setSearch("")}>
<X size={14} color="#94a3b8" strokeWidth={2.5} />
</Pressable>
)}
</View>
</View>
{/* Add New Customer */}
<View className="px-5 pb-5">
<Pressable
onPress={() => { setShow(false); nav.go("customers/create"); }}
className="bg-primary rounded-[6px] py-3.5 flex-row items-center justify-center gap-2"
>
<Plus size={16} color="white" strokeWidth={2.5} />
<Text className="text-white font-sans-bold text-sm">Add New Customer</Text>
</Pressable>
</View>
<ScrollView
className="px-5"
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 40 }}
>
{loading ? (
<View className="py-8 items-center">
<ActivityIndicator color="#E46212" size="small" />
</View>
) : filtered.length === 0 ? (
<View className="py-8 items-center">
<Text className="text-muted-foreground text-sm font-sans-medium">
{search ? "No customers match your search" : "No customers found"}
</Text>
</View>
) : (
filtered.map((c: any) => {
const isCompany = c.type === "COMPANY";
return (
<Pressable
key={c.id}
onPress={() => {
setShow(false);
onSelect({
name: c.displayName || `${c.firstName || ""} ${c.lastName || ""}`.trim() || c.companyName || "",
email: c.email || "",
phone: c.phone || "",
});
}}
className="bg-card rounded-[6px] border border-border p-4 mb-3"
>
<View className="flex-row items-center gap-3">
<View className={`h-9 w-9 rounded-full items-center justify-center ${isCompany ? "bg-blue-500/10" : "bg-primary/10"}`}>
{isCompany ? (
<Building2 color="#2563eb" size={16} strokeWidth={2} />
) : (
<User color="#E46212" size={16} strokeWidth={2} />
)}
</View>
<View className="flex-1">
<Text className="text-foreground font-sans-bold text-sm">
{c.displayName || `${c.firstName || ""} ${c.lastName || ""}`.trim() || c.companyName || "—"}
</Text>
{c.email && (
<Text className="text-muted-foreground text-[11px] font-sans-medium mt-0.5">
{c.email}
</Text>
)}
</View>
<View className={`px-2 py-0.5 rounded-[3px] ${isCompany ? "bg-blue-500/10" : "bg-primary/10"}`}>
<Text className={`text-[8px] font-sans-bold uppercase tracking-widest ${isCompany ? "text-blue-600" : "text-primary"}`}>
{isCompany ? "Company" : "Individual"}
</Text>
</View>
</View>
</Pressable>
);
})
)}
</ScrollView>
</Pressable>
</View>
</Pressable>
</Modal>
</>
);
}