From 4d821646cfccd0f69d81ed7973ac2f2e4449663e Mon Sep 17 00:00:00 2001 From: Tran Anh Tuan Date: Fri, 7 Nov 2025 18:54:44 +0700 Subject: [PATCH] =?UTF-8?q?c=E1=BA=ADp=20nh=E1=BA=ADt=20modal=20add/edit?= =?UTF-8?q?=20fishingLog=20v=C3=A0=20s=E1=BB=ADa=20l=E1=BB=97i=20event=20?= =?UTF-8?q?=E1=BB=9F=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/_layout.tsx | 22 +- app/(tabs)/diary.tsx | 219 ++++++++++-------- app/(tabs)/index.tsx | 146 ++++++------ components/ButtonCreateNewHaulOrTrip.tsx | 7 +- components/tripInfo/NetListTable.tsx | 53 ++++- .../modal/CreateOrUpdateHaulModal.tsx | 20 +- .../modal/NetDetailModal/NetDetailModal.tsx | 5 +- .../NetDetailModal/components/InfoSection.tsx | 29 ++- controller/typings.d.ts | 4 +- services/device_events.ts | 8 + 10 files changed, 300 insertions(+), 213 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 56a202b..bce1985 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,12 +1,32 @@ -import { Tabs } from "expo-router"; +import { Tabs, useSegments } from "expo-router"; import { HapticTab } from "@/components/haptic-tab"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { Colors } from "@/constants/theme"; import { useColorScheme } from "@/hooks/use-color-scheme"; +import { startEvents, stopEvents } from "@/services/device_events"; +import { useEffect, useRef } from "react"; export default function TabLayout() { const colorScheme = useColorScheme(); + const segments = useSegments(); + const prev = useRef(null); + const currentSegment = segments[1] ?? segments[segments.length - 1] ?? null; // tuỳ cấu trúc của bạn + useEffect(() => { + if (prev.current !== currentSegment) { + // console.log("Tab changed ->", { from: prev.current, to: currentSegment }); + // TODO: xử lý khi chuyển tab ở đây + if (prev.current === "(tabs)" && currentSegment !== "(tabs)") { + stopEvents(); + // console.log("Stop events"); + } else if (prev.current !== "(tabs)" && currentSegment === "(tabs)") { + // we came back into the tabs group — restart polling + startEvents(); + // console.log("start events"); + } + prev.current = currentSegment; + } + }, [currentSegment]); return ( { + getGpsEventBus(); + getAlarmEventBus(); + getEntitiesEventBus(); + getBanzonesEventBus(); + getTrackPointsEventBus(); + const queryGpsData = (gpsData: Model.GPSResponse) => { + if (gpsData) { + // console.log("GPS Data: ", gpsData); + setGpsData(gpsData); + } else { + setGpsData(null); + setPolygonCoordinates([]); + setPolylineCoordinates(null); + } + }; + const queryAlarmData = (alarmData: Model.AlarmResponse) => { + // console.log("Alarm Data: ", alarmData.alarms.length); + setAlarmData(alarmData); + }; + const queryEntityData = (entityData: Model.TransformedEntity[]) => { + // console.log("Entities Length Data: ", entityData.length); + setEntityData(entityData); + }; + const queryBanzonesData = (banzoneData: Model.Zone[]) => { + // console.log("Banzone Data: ", banzoneData.length); - // const [number, setNumber] = useState(0); + setBanzoneData(banzoneData); + }; + const queryTrackPointsData = (TrackPointsData: Model.ShipTrackPoint[]) => { + // console.log("TrackPoints Data: ", TrackPointsData.length); + if (TrackPointsData && TrackPointsData.length > 0) { + setTrackPointsData(TrackPointsData); + } else { + setTrackPointsData(null); + } + }; - // useEffect(() => { - // getGpsEventBus(); - // getAlarmEventBus(); - // getEntitiesEventBus(); - // getBanzonesEventBus(); - // getTrackPointsEventBus(); - // const queryGpsData = (gpsData: Model.GPSResponse) => { - // if (gpsData) { - // // console.log("GPS Data: ", gpsData); - // setGpsData(gpsData); - // } else { - // setGpsData(null); - // setPolygonCoordinates([]); - // setPolylineCoordinates(null); - // } - // }; - // const queryAlarmData = (alarmData: Model.AlarmResponse) => { - // // console.log("Alarm Data: ", alarmData.alarms.length); - // setAlarmData(alarmData); - // }; - // const queryEntityData = (entityData: Model.TransformedEntity[]) => { - // // console.log("Entities Length Data: ", entityData.length); - // setEntityData(entityData); - // }; - // const queryBanzonesData = (banzoneData: Model.Zone[]) => { - // // console.log("Banzone Data: ", banzoneData.length); + eventBus.on(EVENT_GPS_DATA, queryGpsData); + // console.log("Registering event handlers in HomeScreen"); + eventBus.on(EVENT_GPS_DATA, queryGpsData); + // console.log("Subscribed to EVENT_GPS_DATA"); + eventBus.on(EVENT_ALARM_DATA, queryAlarmData); + // console.log("Subscribed to EVENT_ALARM_DATA"); + eventBus.on(EVENT_ENTITY_DATA, queryEntityData); + // console.log("Subscribed to EVENT_ENTITY_DATA"); + eventBus.on(EVENT_TRACK_POINTS_DATA, queryTrackPointsData); + // console.log("Subscribed to EVENT_TRACK_POINTS_DATA"); + eventBus.once(EVENT_BANZONE_DATA, queryBanzonesData); + // console.log("Subscribed once to EVENT_BANZONE_DATA"); - // setBanzoneData(banzoneData); - // }; - // const queryTrackPointsData = (TrackPointsData: Model.ShipTrackPoint[]) => { - // // console.log("TrackPoints Data: ", TrackPointsData.length); - // if (TrackPointsData && TrackPointsData.length > 0) { - // setTrackPointsData(TrackPointsData); - // } else { - // setTrackPointsData(null); - // } - // }; - - // eventBus.on(EVENT_GPS_DATA, queryGpsData); - // // console.log("Registering event handlers in HomeScreen"); - // eventBus.on(EVENT_GPS_DATA, queryGpsData); - // // console.log("Subscribed to EVENT_GPS_DATA"); - // eventBus.on(EVENT_ALARM_DATA, queryAlarmData); - // // console.log("Subscribed to EVENT_ALARM_DATA"); - // eventBus.on(EVENT_ENTITY_DATA, queryEntityData); - // // console.log("Subscribed to EVENT_ENTITY_DATA"); - // eventBus.on(EVENT_TRACK_POINTS_DATA, queryTrackPointsData); - // // console.log("Subscribed to EVENT_TRACK_POINTS_DATA"); - // eventBus.once(EVENT_BANZONE_DATA, queryBanzonesData); - // // console.log("Subscribed once to EVENT_BANZONE_DATA"); - - // return () => { - // // console.log("Unregistering event handlers in HomeScreen"); - // eventBus.off(EVENT_GPS_DATA, queryGpsData); - // // console.log("Unsubscribed EVENT_GPS_DATA"); - // eventBus.off(EVENT_ALARM_DATA, queryAlarmData); - // // console.log("Unsubscribed EVENT_ALARM_DATA"); - // eventBus.off(EVENT_ENTITY_DATA, queryEntityData); - // // console.log("Unsubscribed EVENT_ENTITY_DATA"); - // eventBus.off(EVENT_TRACK_POINTS_DATA, queryTrackPointsData); - // // console.log("Unsubscribed EVENT_TRACK_POINTS_DATA"); - // }; - // }, []); + return () => { + // console.log("Unregistering event handlers in HomeScreen"); + eventBus.off(EVENT_GPS_DATA, queryGpsData); + // console.log("Unsubscribed EVENT_GPS_DATA"); + eventBus.off(EVENT_ALARM_DATA, queryAlarmData); + // console.log("Unsubscribed EVENT_ALARM_DATA"); + eventBus.off(EVENT_ENTITY_DATA, queryEntityData); + // console.log("Unsubscribed EVENT_ENTITY_DATA"); + eventBus.off(EVENT_TRACK_POINTS_DATA, queryTrackPointsData); + // console.log("Unsubscribed EVENT_TRACK_POINTS_DATA"); + }; + }, []); useEffect(() => { setPolylineCoordinates(null); @@ -162,7 +174,7 @@ export default function HomeScreen() { // foundPolygon = true; const coordinates = convertWKTtoLatLngString(geom_poly || ""); if (coordinates.length > 0) { - console.log("Polygon Coordinate: ", coordinates); + // console.log("Polygon Coordinate: ", coordinates); setPolygonCoordinates( coordinates.map((polygon) => ({ coordinates: polygon.map((coord) => ({ diff --git a/components/ButtonCreateNewHaulOrTrip.tsx b/components/ButtonCreateNewHaulOrTrip.tsx index 2808b9a..97270a9 100644 --- a/components/ButtonCreateNewHaulOrTrip.tsx +++ b/components/ButtonCreateNewHaulOrTrip.tsx @@ -94,7 +94,7 @@ const ButtonCreateNewHaulOrTrip: React.FC = ({ }); if (resp.status === 200) { showSuccessToast("Bắt đầu chuyến đi thành công!"); - getTrip(); + await getTrip(); } } catch (error) { console.error("Error stating trip :", error); @@ -125,7 +125,7 @@ const ButtonCreateNewHaulOrTrip: React.FC = ({ const resp = await queryStartNewHaul(body); if (resp.status === 200) { showSuccessToast("Bắt đầu mẻ lưới mới thành công!"); - getTrip(); + await getTrip(); } else { showErrorToast("Tạo mẻ lưới mới thất bại!"); } @@ -173,7 +173,8 @@ const ButtonCreateNewHaulOrTrip: React.FC = ({ )} f.status === 0)!} + fishingLogIndex={trip?.fishing_logs?.length!} isVisible={isFinishHaulModalOpen} onClose={function (): void { setIsFinishHaulModalOpen(false); diff --git a/components/tripInfo/NetListTable.tsx b/components/tripInfo/NetListTable.tsx index 2b4b5f6..7bde79c 100644 --- a/components/tripInfo/NetListTable.tsx +++ b/components/tripInfo/NetListTable.tsx @@ -3,7 +3,7 @@ import { useFishes } from "@/state/use-fish"; import { useTrip } from "@/state/use-trip"; import React, { useEffect, useRef, useState } from "react"; import { Animated, Text, TouchableOpacity, View } from "react-native"; -import NetDetailModal from "./modal/NetDetailModal/NetDetailModal"; +import CreateOrUpdateHaulModal from "./modal/CreateOrUpdateHaulModal"; import styles from "./style/NetListTable.styles"; const NetListTable: React.FC = () => { @@ -17,8 +17,11 @@ const NetListTable: React.FC = () => { useEffect(() => { getFishSpecies(); }, []); - const data: Model.FishingLog[] = trip?.fishing_logs ?? []; - const tongSoMe = data.length; + + // useEffect(() => { + // console.log("Trip thay đổi: ", trip?.fishing_logs?.length); + // }, [trip]); + // const data: Model.FishingLog[] = trip?.fishing_logs ?? []; const handleToggle = () => { const toValue = collapsed ? contentHeight : 0; @@ -31,7 +34,7 @@ const NetListTable: React.FC = () => { }; const handleStatusPress = (id: string) => { - const net = data.find((item) => item.fishing_log_id === id); + const net = trip?.fishing_logs?.find((item) => item.fishing_log_id === id); if (net) { setSelectedNet(net); setModalVisible(true); @@ -47,7 +50,11 @@ const NetListTable: React.FC = () => { style={styles.headerRow} > Danh sách mẻ lưới - {collapsed && {tongSoMe}} + {collapsed && ( + + {trip?.fishing_logs?.length} + + )} { style={{ position: "absolute", opacity: 0, zIndex: -1000 }} onLayout={(event) => { const height = event.nativeEvent.layout.height; - if (height > 0 && contentHeight === 0) { + // Update measured content height whenever it actually changes. + if (height > 0 && height !== contentHeight) { setContentHeight(height); + // If the panel is currently expanded, animate to the new height so + // newly added/removed rows become visible immediately. + if (!collapsed) { + Animated.timing(animatedHeight, { + toValue: height, + duration: 200, + useNativeDriver: false, + }).start(); + } } }} > @@ -72,7 +89,7 @@ const NetListTable: React.FC = () => { {/* Body */} - {data.map((item, index) => ( + {trip?.fishing_logs?.map((item, index) => ( {/* Cột STT */} Mẻ {index + 1} @@ -101,7 +118,7 @@ const NetListTable: React.FC = () => { {/* Body */} - {data.map((item, index) => ( + {trip?.fishing_logs?.map((item, index) => ( {/* Cột STT */} Mẻ {index + 1} @@ -120,9 +137,23 @@ const NetListTable: React.FC = () => { ))} - + { + console.log("OnCLose"); + setModalVisible(false); + }} + fishingLog={selectedNet} + fishingLogIndex={ + selectedNet + ? trip!.fishing_logs!.findIndex( + (item) => item.fishing_log_id === selectedNet.fishing_log_id + ) + 1 + : undefined + } + /> {/* Modal chi tiết */} - { console.log("OnCLose"); @@ -136,7 +167,7 @@ const NetListTable: React.FC = () => { ) + 1 : undefined } - /> + /> */} ); }; diff --git a/components/tripInfo/modal/CreateOrUpdateHaulModal.tsx b/components/tripInfo/modal/CreateOrUpdateHaulModal.tsx index 41b5d04..4800af8 100644 --- a/components/tripInfo/modal/CreateOrUpdateHaulModal.tsx +++ b/components/tripInfo/modal/CreateOrUpdateHaulModal.tsx @@ -5,11 +5,13 @@ import React from "react"; import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Button, FlatList, Modal, Text, TextInput, View } from "react-native"; import { z } from "zod"; +import { InfoSection } from "./NetDetailModal/components"; interface CreateOrUpdateHaulModalProps { isVisible: boolean; onClose: () => void; - haulData?: Model.FishingLogInfo[] | null; + fishingLog?: Model.FishingLog | null; + fishingLogIndex?: number; } const UNITS = ["con", "kg", "tấn"] as const; @@ -49,9 +51,10 @@ const defaultItem = (): FormValues["fish"][number] => ({ const CreateOrUpdateHaulModal: React.FC = ({ isVisible, onClose, - haulData, + fishingLog, + fishingLogIndex }) => { - const [isCreateMode, setIsCreateMode] = React.useState(!haulData); + const [isCreateMode, setIsCreateMode] = React.useState(!fishingLog?.info); const { control, handleSubmit, formState, watch, reset } = useForm({ resolver: zodResolver(formSchema), @@ -102,14 +105,14 @@ const CreateOrUpdateHaulModal: React.FC = ({ return; } - // when modal opened, populate based on haulData - if (haulData === null) { + // when modal opened, populate based on fishingLog + if (fishingLog?.info === null) { // explicit null -> start with a single default item reset({ fish: [defaultItem()] }); setIsCreateMode(true); - } else if (Array.isArray(haulData) && haulData.length > 0) { + } else if (Array.isArray(fishingLog?.info) && fishingLog?.info.length > 0) { // map FishingLogInfo -> form rows - const mapped = haulData.map((h) => ({ + 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), @@ -122,7 +125,7 @@ const CreateOrUpdateHaulModal: React.FC = ({ reset({ fish: [defaultItem()] }); setIsCreateMode(true); } - }, [isVisible, haulData, reset]); + }, [isVisible, fishingLog?.info, reset]); const renderRow = ({ item, index }: { item: any; index: number }) => { return ( = ({ onRequestClose={onClose} > {isCreateMode ? "Create Haul" : "Update Haul"} + it._id} diff --git a/components/tripInfo/modal/NetDetailModal/NetDetailModal.tsx b/components/tripInfo/modal/NetDetailModal/NetDetailModal.tsx index dd5719d..911ec6a 100644 --- a/components/tripInfo/modal/NetDetailModal/NetDetailModal.tsx +++ b/components/tripInfo/modal/NetDetailModal/NetDetailModal.tsx @@ -11,7 +11,6 @@ import { import styles from "../style/NetDetailModal.styles"; import { CatchSectionHeader } from "./components/CatchSectionHeader"; import { FishCardList } from "./components/FishCardList"; -import { InfoSection } from "./components/InfoSection"; import { NotesSection } from "./components/NotesSection"; interface NetDetailModalProps { @@ -320,11 +319,11 @@ const NetDetailModal: React.FC = ({ {/* Content */} {/* Thông tin chung */} - + /> */} {/* Danh sách cá bắt được */} diff --git a/components/tripInfo/modal/NetDetailModal/components/InfoSection.tsx b/components/tripInfo/modal/NetDetailModal/components/InfoSection.tsx index e33ecda..3d105ec 100644 --- a/components/tripInfo/modal/NetDetailModal/components/InfoSection.tsx +++ b/components/tripInfo/modal/NetDetailModal/components/InfoSection.tsx @@ -3,53 +3,52 @@ import { Text, View } from "react-native"; import styles from "../../style/NetDetailModal.styles"; interface InfoSectionProps { - netData?: Model.FishingLog; - isCompleted: boolean; + fishingLog?: Model.FishingLog; stt?: number; } export const InfoSection: React.FC = ({ - netData, - isCompleted, + fishingLog, stt, }) => { - if (!netData) { + if (!fishingLog) { return null; } const infoItems = [ { label: "Số thứ tự", value: `Mẻ ${stt}` }, { label: "Trạng thái", - value: netData.status === 1 ? "Đã hoàn thành" : "Chưa hoàn thành", + value: fishingLog.status === 1 ? "Đã hoàn thành" : "Chưa hoàn thành", isStatus: true, }, { label: "Thời gian bắt đầu", - value: netData.start_at - ? new Date(netData.start_at).toLocaleString() + value: fishingLog.start_at + ? new Date(fishingLog.start_at).toLocaleString() : "Chưa cập nhật", }, { label: "Thời gian kết thúc", - value: netData.start_at - ? new Date(netData.end_at).toLocaleString() - : "Chưa cập nhật", + value: + fishingLog.end_at !== "0001-01-01T00:00:00Z" + ? new Date(fishingLog.end_at).toLocaleString() + : "-", }, // { // label: "Vị trí hạ thu", - // value: netData.viTriHaThu || "Chưa cập nhật", + // value: fishingLog.viTriHaThu || "Chưa cập nhật", // }, // { // label: "Vị trí thu lưới", - // value: netData.viTriThuLuoi || "Chưa cập nhật", + // value: fishingLog.viTriThuLuoi || "Chưa cập nhật", // }, // { // label: "Độ sâu hạ thu", - // value: netData.doSauHaThu || "Chưa cập nhật", + // value: fishingLog.doSauHaThu || "Chưa cập nhật", // }, // { // label: "Độ sâu thu lưới", - // value: netData.doSauThuLuoi || "Chưa cập nhật", + // value: fishingLog.doSauThuLuoi || "Chưa cập nhật", // }, ]; diff --git a/controller/typings.d.ts b/controller/typings.d.ts index 1500520..f30f04e 100644 --- a/controller/typings.d.ts +++ b/controller/typings.d.ts @@ -152,8 +152,8 @@ declare namespace Model { interface FishingLog { fishing_log_id: string; trip_id: string; - start_at: Date; // ISO datetime - end_at: Date; // ISO datetime + start_at: string; // ISO datetime + end_at: string; // ISO datetime start_lat: number; start_lon: number; haul_lat: number; diff --git a/services/device_events.ts b/services/device_events.ts index dcf9279..b3a628d 100644 --- a/services/device_events.ts +++ b/services/device_events.ts @@ -162,3 +162,11 @@ export function stopEvents() { } }); } + +export function startEvents() { + getGpsEventBus(); + getAlarmEventBus(); + getEntitiesEventBus(); + getTrackPointsEventBus(); + getBanzonesEventBus(); +}