import Select from "@/components/Select"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { useFishes } from "@/state/use-fish"; import { zodResolver } from "@hookform/resolvers/zod"; import React from "react"; import { Controller, useFieldArray, useForm } from "react-hook-form"; import { KeyboardAvoidingView, Modal, Platform, ScrollView, Text, TextInput, TouchableOpacity, View, } from "react-native"; import { z } from "zod"; import { InfoSection } from "./NetDetailModal/components"; import styles from "./style/CreateOrUpdateHaulModal.styles"; interface CreateOrUpdateHaulModalProps { isVisible: boolean; onClose: () => void; fishingLog?: Model.FishingLog | null; fishingLogIndex?: number; } const UNITS = ["con", "kg", "tấn"] as const; type Unit = (typeof UNITS)[number]; const UNITS_OPTIONS = UNITS.map((unit) => ({ label: unit, value: unit.toString(), })); // Zod schema cho 1 dòng cá const fishItemSchema = z.object({ id: z.number().min(1, "Chọn loài cá"), quantity: z .number({ invalid_type_error: "Số lượng phải là số" }) .positive("Số lượng > 0"), unit: z.enum(UNITS, { required_error: "Chọn đơn vị" }), size: z .number({ invalid_type_error: "Kích thước phải là số" }) .positive("Kích thước > 0") .optional(), }); // Schema tổng: mảng các item const formSchema = z.object({ fish: z.array(fishItemSchema).min(1, "Thêm ít nhất 1 loài cá"), }); type FormValues = z.infer; const defaultItem = (): FormValues["fish"][number] => ({ id: -1, quantity: 1, unit: "con", size: undefined, }); const CreateOrUpdateHaulModal: React.FC = ({ isVisible, onClose, fishingLog, fishingLogIndex, }) => { const [isCreateMode, setIsCreateMode] = React.useState(!fishingLog?.info); const [isEditing, setIsEditing] = React.useState(false); const [expandedFishIndices, setExpandedFishIndices] = React.useState< number[] >([]); const { control, handleSubmit, formState, watch, reset } = useForm({ resolver: zodResolver(formSchema), defaultValues: { fish: [defaultItem()], }, mode: "onSubmit", }); const { fishSpecies, getFishSpecies } = useFishes(); const { errors } = formState; if (!fishSpecies) { getFishSpecies(); } const { fields, append, remove } = useFieldArray({ control, name: "fish", keyName: "_id", // tránh đụng key }); const handleToggleExpanded = (index: number) => { setExpandedFishIndices((prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index] ); }; const onSubmit = (values: FormValues) => { // Map form values to the FishingLogInfo-like shape the user requested const mapped = values.fish.map((f) => { const meta = fishSpecies!.find((x) => x.id === f.id); return { fish_species_id: f.id, fish_name: meta?.name, catch_number: f.quantity, catch_unit: f.unit, fish_size: f.size, fish_rarity: meta?.rarity_level, fish_condition: "", gear_usage: "", } as unknown; // inferred shape — keep as unknown to avoid relying on global types here }); console.log("SUBMIT (FishingLogInfo[]): ", JSON.stringify(mapped, null, 2)); // close modal after submit (you can change this to pass the payload to a parent via prop) onClose(); }; // Initialize / reset form when modal visibility or haulData changes React.useEffect(() => { if (!isVisible) { // when modal closed, clear form to default reset({ fish: [defaultItem()] }); setIsCreateMode(true); setIsEditing(false); setExpandedFishIndices([]); return; } // when modal opened, populate based on fishingLog if (fishingLog?.info === null) { // explicit null -> start with a single default item reset({ fish: [defaultItem()] }); setIsCreateMode(true); setIsEditing(true); // allow editing for new haul setExpandedFishIndices([0]); // expand first item } else if (Array.isArray(fishingLog?.info) && fishingLog?.info.length > 0) { // map FishingLogInfo -> form rows const mapped = fishingLog.info.map((h) => ({ id: h.fish_species_id ?? -1, quantity: (h.catch_number as number) ?? 1, unit: (h.catch_unit as Unit) ?? (defaultItem().unit as Unit), size: (h.fish_size as number) ?? undefined, })); reset({ fish: mapped as any }); setIsCreateMode(false); setIsEditing(false); // view mode by default setExpandedFishIndices([]); // all collapsed } else { // undefined or empty array -> default reset({ fish: [defaultItem()] }); setIsCreateMode(true); setIsEditing(true); // allow editing for new haul setExpandedFishIndices([0]); // expand first item } }, [isVisible, fishingLog?.info, reset]); const renderRow = (item: any, index: number) => { const isExpanded = expandedFishIndices.includes(index); return ( {/* Delete + Chevron buttons - top right corner */} {isEditing && ( remove(index)} style={{ backgroundColor: "#FF3B30", borderRadius: 8, width: 40, height: 40, justifyContent: "center", alignItems: "center", shadowColor: "#000", shadowOpacity: 0.08, shadowRadius: 2, shadowOffset: { width: 0, height: 1 }, elevation: 2, }} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} activeOpacity={0.7} > )} handleToggleExpanded(index)} style={{ backgroundColor: "#007AFF", borderRadius: 8, width: 40, height: 40, justifyContent: "center", alignItems: "center", shadowColor: "#000", shadowOpacity: 0.08, shadowRadius: 2, shadowOffset: { width: 0, height: 1 }, elevation: 2, }} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} activeOpacity={0.7} > {/* Header - visible when collapsed */} {!isExpanded && ( {(() => { const fishId = watch(`fish.${index}.id`); const fishName = fishSpecies?.find((f) => f.id === fishId)?.name; const quantity = watch(`fish.${index}.quantity`); const unit = watch(`fish.${index}.unit`); return ( {fishName || "Chọn loài cá"}: {fishName ? `${quantity} ${unit}` : "---"} ); })()} )} {/* Form - visible when expanded */} {isExpanded && ( {/* Species dropdown */} ( Tên cá ({ label: unit.label, value: unit.value, }))} value={value} onChange={onChange} placeholder="Chọn đơn vị" disabled={!isEditing} listStyle={{ maxHeight: 100 }} /> {errors.fish?.[index]?.unit && ( {errors.fish[index]?.unit?.message as string} )} )} /> {/* Size (optional) */} ( Kích thước (cm) — tùy chọn onChange(t ? Number(t.replace(/,/g, ".")) : undefined) } style={[styles.input, !isEditing && styles.inputDisabled]} editable={isEditing} /> {errors.fish?.[index]?.size && ( {errors.fish[index]?.size?.message as string} )} )} /> )} ); }; return ( {/* Header */} {isCreateMode ? "Thêm mẻ cá" : "Chỉnh sửa mẻ cá"} {isEditing ? ( <> { setIsEditing(false); reset(); // reset to previous values }} style={[styles.saveButton, { backgroundColor: "#6c757d" }]} > Hủy Lưu ) : ( !isCreateMode && ( setIsEditing(true)} style={[styles.saveButton, { backgroundColor: "#17a2b8" }]} > Sửa ) )} {/* Content */} {/* Info Section */} {/* Fish List */} {fields.map((item, index) => renderRow(item, index))} {/* Add Button - only show when editing */} {isEditing && ( append(defaultItem())} style={styles.addButton} > + Thêm loài cá )} {/* Error Message */} {errors.fish && ( {(errors.fish as any)?.message} )} ); }; export default CreateOrUpdateHaulModal;