import Select from "@/components/Select"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { queryGpsData } from "@/controller/DeviceController"; import { queryUpdateFishingLogs } from "@/controller/TripController"; import { showErrorToast, showSuccessToast } from "@/services/toast_service"; import { useFishes } from "@/state/use-fish"; import { useTrip } from "@/state/use-trip"; 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 { trip, getTrip } = useTrip(); 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 = async (values: FormValues) => { // Ensure species list is available so we can populate name/rarity if (!fishSpecies || fishSpecies.length === 0) { showErrorToast("Danh sách loài cá chưa sẵn sàng"); return; } // Helper to map form rows -> API info entries (single place) const buildInfo = (rows: FormValues["fish"]) => rows.map((item) => { const meta = fishSpecies.find((f) => f.id === item.id); return { fish_species_id: item.id, fish_name: meta?.name ?? "", catch_number: item.quantity, catch_unit: item.unit, fish_size: item.size, fish_rarity: meta?.rarity_level ?? null, fish_condition: "", gear_usage: "", } as unknown; }); try { const gpsResp = await queryGpsData(); if (!gpsResp.data) { showErrorToast("Không thể lấy dữ liệu GPS hiện tại"); return; } const gpsData = gpsResp.data; const info = buildInfo(values.fish) as any; // Base payload fields shared between create and update const base: Partial = { fishing_log_id: fishingLog?.fishing_log_id || "", trip_id: trip?.id || "", start_at: fishingLog?.start_at!, start_lat: fishingLog?.start_lat!, start_lon: fishingLog?.start_lon!, weather_description: fishingLog?.weather_description || "Nắng đẹp, Trời nhiều mây", info, sync: true, }; // Build final payload depending on create vs update const body: Model.FishingLog = fishingLog?.status == 0 ? ({ ...base, haul_lat: gpsData.lat, haul_lon: gpsData.lon, end_at: new Date(), status: 1, } as Model.FishingLog) : ({ ...base, haul_lat: fishingLog?.haul_lat, haul_lon: fishingLog?.haul_lon, end_at: fishingLog?.end_at, status: fishingLog?.status, } as Model.FishingLog); // console.log("Body: ", body); const resp = await queryUpdateFishingLogs(body); if (resp?.status === 200) { showSuccessToast( fishingLog?.fishing_log_id == null ? "Thêm mẻ cá thành công" : "Cập nhật mẻ cá thành công" ); getTrip(); onClose(); } else { showErrorToast( fishingLog?.fishing_log_id == null ? "Thêm mẻ cá thất bại" : "Cập nhật mẻ cá thất bại" ); } } catch (err) { console.error("onSubmit error:", err); showErrorToast("Có lỗi xảy ra khi lưu mẻ cá"); } }; // 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;