Yaltopia-Tickets-App/app/customers/create.tsx
2026-06-05 13:39:37 +03:00

480 lines
17 KiB
TypeScript

import React, { useState } from "react";
import { View, Pressable, TextInput, StyleSheet } from "react-native";
import { useSirouRouter } from "@sirou/react-native";
import { useColorScheme } from "nativewind";
import { api } from "@/lib/api";
import { AppRoutes } from "@/lib/routes";
import { toast } from "@/lib/toast-store";
import { ScreenWrapper } from "@/components/ScreenWrapper";
import { FormFlow } from "@/components/FormFlow";
import { Text } from "@/components/ui/text";
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>
);
}
const TYPES = ["INDIVIDUAL", "COMPANY"] as const;
const STEPS = [
{ key: "type", label: "Type" },
{ key: "details", label: "Details" },
{ key: "documents", label: "Documents" },
{ key: "notes", label: "Notes" },
{ key: "summary", label: "Summary" },
];
export default function CreateCustomerScreen() {
const nav = useSirouRouter<AppRoutes>();
const [step, setStep] = useState(0);
const [submitting, setSubmitting] = useState(false);
const c = useInputColors();
const [type, setType] = useState<"INDIVIDUAL" | "COMPANY">("INDIVIDUAL");
const [displayName, setDisplayName] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [companyName, setCompanyName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [tin, setTin] = useState("");
const [vatReg, setVatReg] = useState("");
const [businessLicense, setBusinessLicense] = useState("");
const [address, setAddress] = useState("");
const [notes, setNotes] = useState("");
const handleNext = () => {
if (step === 0 && !displayName.trim()) {
toast.error("Validation", "Display name is required");
return;
}
if (step === 1 && type === "INDIVIDUAL" && !firstName.trim()) {
toast.error("Validation", "First name is required");
return;
}
if (step === 1 && type === "COMPANY" && !companyName.trim()) {
toast.error("Validation", "Company name is required");
return;
}
setStep(step + 1);
};
const handleSubmit = async () => {
const body: Record<string, any> = {
type,
displayName,
email: email || undefined,
phone: phone ? `+251${phone.replace(/^\+/, "")}` : undefined,
tin: tin || undefined,
vatRegistrationNumber: vatReg || undefined,
businessLicenseNumber: businessLicense || undefined,
address: address || undefined,
firstName: firstName || undefined,
lastName: lastName || undefined,
companyName: companyName || undefined,
notes: notes || undefined,
};
Object.keys(body).forEach((k) => body[k] === undefined && delete body[k]);
try {
setSubmitting(true);
await api.customers.create({ body });
toast.success("Success", "Customer created successfully!");
nav.back();
} catch (err: any) {
toast.error("Error", err?.message || "Failed to create customer");
} 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 Customer"
>
{step === 0 && (
<View className="gap-5">
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
Customer Type
</Text>
<View className="bg-card rounded-[6px] gap-4">
<View className="flex-row gap-2">
{TYPES.map((t) => (
<Pressable
key={t}
onPress={() => setType(t)}
className={`flex-1 py-3 rounded-[6px] items-center border ${
type === t
? "bg-primary border-primary"
: "bg-card border-border"
}`}
>
<Text
className={`text-[11px] font-sans-bold uppercase tracking-widest ${
type === t ? "text-white" : "text-foreground"
}`}
>
{t === "INDIVIDUAL" ? "Individual" : "Company"}
</Text>
</Pressable>
))}
</View>
<Field
label="Display Name"
value={displayName}
onChangeText={setDisplayName}
placeholder="e.g. John Doe or Acme Corp"
/>
{type === "INDIVIDUAL" && (
<Field
label="Email"
value={email}
onChangeText={setEmail}
placeholder="john@example.com"
/>
)}
</View>
</View>
)}
{step === 1 && (
<View className="gap-5">
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
{type === "INDIVIDUAL" ? "Personal Details" : "Company Details"}
</Text>
<View className="bg-card rounded-[6px] gap-4">
{type === "INDIVIDUAL" ? (
<>
<View className="flex-row gap-4">
<Field
label="First Name"
value={firstName}
onChangeText={setFirstName}
placeholder="John"
flex={1}
/>
<Field
label="Last Name"
value={lastName}
onChangeText={setLastName}
placeholder="Doe"
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={phone}
onChangeText={setPhone}
keyboardType="phone-pad"
maxLength={9}
style={{ textAlignVertical: "center" }}
/>
</View>
</View>
</>
) : (
<>
<Field
label="Company Name"
value={companyName}
onChangeText={setCompanyName}
placeholder="Acme Corp"
/>
<View className="flex-row gap-4">
<Field
label="Email"
value={email}
onChangeText={setEmail}
placeholder="info@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={phone}
onChangeText={setPhone}
keyboardType="phone-pad"
maxLength={9}
style={{ textAlignVertical: "center" }}
/>
</View>
</View>
</>
)}
<Field
label="Address"
value={address}
onChangeText={setAddress}
placeholder="e.g. Bole Road, Addis Ababa"
/>
</View>
</View>
)}
{step === 2 && (
<View className="gap-5">
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
Documents
</Text>
<View className="bg-card rounded-[6px] gap-4">
<Field
label="TIN Number"
value={tin}
onChangeText={setTin}
placeholder="e.g. 1234567890"
/>
<Field
label="VAT Registration"
value={vatReg}
onChangeText={setVatReg}
placeholder="e.g. VAT-12345"
/>
<Field
label="Business License"
value={businessLicense}
onChangeText={setBusinessLicense}
placeholder="e.g. BL-2024-001"
/>
</View>
</View>
)}
{step === 3 && (
<View className="gap-5">
<View className="bg-card rounded-[6px] gap-4">
<Field
label="Notes"
value={notes}
onChangeText={setNotes}
placeholder="Any additional notes..."
multiline
/>
</View>
</View>
)}
{step === 4 && (
<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-medium">
Type
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{type === "INDIVIDUAL" ? "Individual" : "Company"}
</Text>
</View>
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Display Name
</Text>
<Text className="text-[14px] text-foreground font-sans-bold text-right flex-1 ml-4">
{displayName}
</Text>
</View>
{firstName ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
First Name
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{firstName}
</Text>
</View>
) : null}
{lastName ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Last Name
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{lastName}
</Text>
</View>
) : null}
{companyName ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Company
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{companyName}
</Text>
</View>
) : null}
{email ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Email
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{email}
</Text>
</View>
) : null}
{phone ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Phone
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
+251{phone}
</Text>
</View>
) : null}
{tin ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
TIN
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{tin}
</Text>
</View>
) : null}
{vatReg ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
VAT Reg
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{vatReg}
</Text>
</View>
) : null}
{businessLicense ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Business License
</Text>
<Text className="text-[14px] text-foreground font-sans-bold">
{businessLicense}
</Text>
</View>
) : null}
{address ? (
<View className="flex-row justify-between">
<Text className="text-[14px] text-foreground font-sans-medium">
Address
</Text>
<Text
className="text-[14px] text-foreground font-sans-bold text-right flex-1 ml-4"
numberOfLines={2}
>
{address}
</Text>
</View>
) : null}
{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 text-right flex-1 ml-4"
numberOfLines={2}
>
{notes}
</Text>
</View>
) : null}
</View>
</View>
)}
</FormFlow>
</ScreenWrapper>
);
}