import { useCallback, useEffect, useRef, useState } from "react"; import { ActivityIndicator, Alert, FlatList, Platform, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { Ionicons } from "@expo/vector-icons"; import { useRouter } from "expo-router"; import FilterButton from "@/components/diary/FilterButton"; import TripCard from "@/components/diary/TripCard"; import FilterModal, { FilterValues } from "@/components/diary/FilterModal"; import TripFormModal from "@/components/diary/TripFormModal"; import { useThings } from "@/state/use-thing"; import { useTripsList } from "@/state/use-tripslist"; import dayjs from "dayjs"; import { useI18n } from "@/hooks/use-i18n"; import { useThemeContext } from "@/hooks/use-theme-context"; import { useShip } from "@/state/use-ship"; export default function diary() { const { t } = useI18n(); const { colors } = useThemeContext(); const router = useRouter(); const [showFilterModal, setShowFilterModal] = useState(false); const [showTripFormModal, setShowTripFormModal] = useState(false); const [editingTrip, setEditingTrip] = useState(null); const [filters, setFilters] = useState({ status: null, startDate: null, endDate: null, selectedShip: null, // Tàu được chọn }); // State for pagination const [allTrips, setAllTrips] = useState([]); const [isLoadingMore, setIsLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(true); const [refreshing, setRefreshing] = useState(false); const isInitialLoad = useRef(true); const flatListRef = useRef(null); // Body call API things (đang fix cứng) const payloadThings: Model.SearchThingBody = { offset: 0, limit: 200, order: "name", dir: "asc", metadata: { not_empty: "ship_name, ship_reg_number", }, }; // Gọi API things nếu chưa có dữ liệu const { things, getThings } = useThings(); useEffect(() => { if (!things) { getThings(payloadThings); } }, [things, getThings]); // Gọi API ships nếu chưa có dữ liệu const { ships, getShip } = useShip(); useEffect(() => { if (!ships) { getShip(); } }, [ships, getShip]); // State cho payload trips const [payloadTrips, setPayloadTrips] = useState({ name: "", order: "", dir: "desc", offset: 0, limit: 10, metadata: { from: "", to: "", ship_name: "", reg_number: "", province_code: "", owner_id: "", ship_id: "", status: "", }, }); const { tripsList, getTripsList, loading } = useTripsList(); // Gọi API trips lần đầu useEffect(() => { if (!isInitialLoad.current) return; isInitialLoad.current = false; setAllTrips([]); setHasMore(true); getTripsList(payloadTrips); }, []); // Xử lý khi nhận được dữ liệu mới từ API useEffect(() => { if (tripsList?.trips && tripsList.trips.length > 0) { if (isInitialLoad.current || payloadTrips.offset === 0) { // Load lần đầu hoặc reset do filter setAllTrips(tripsList.trips); isInitialLoad.current = false; } else { // Load more - append data setAllTrips((prevTrips) => { // Tránh duplicate bằng cách check ID const existingIds = new Set(prevTrips.map((trip) => trip.id)); const newTrips = tripsList.trips!.filter( (trip) => !existingIds.has(trip.id) ); return [...prevTrips, ...newTrips]; }); } // Check if có thêm data không const totalFetched = payloadTrips.offset + tripsList.trips.length; setHasMore(totalFetched < (tripsList.total || 0)); setIsLoadingMore(false); } else if (tripsList && payloadTrips.offset === 0) { // Trường hợp API trả về rỗng khi filter setAllTrips([]); setHasMore(false); setIsLoadingMore(false); } }, [tripsList]); const handleFilter = () => { setShowFilterModal(true); }; const handleApplyFilters = (newFilters: FilterValues) => { setFilters(newFilters); // Cập nhật payload với filter mới và reset offset // Lưu ý: status gửi lên server là string const updatedPayload: Model.TripListBody = { ...payloadTrips, offset: 0, // Reset về đầu khi filter metadata: { ...payloadTrips.metadata, from: newFilters.startDate ? dayjs(newFilters.startDate).startOf("day").toISOString() : "", to: newFilters.endDate ? dayjs(newFilters.endDate).endOf("day").toISOString() : "", // Convert number status sang string để gửi lên server status: newFilters.status !== null ? String(newFilters.status) : "", // Thêm ship_id từ tàu đã chọn ship_name: newFilters.selectedShip?.shipName || "", }, }; // Reset trips khi apply filter mới setAllTrips([]); setHasMore(true); setPayloadTrips(updatedPayload); getTripsList(updatedPayload); setShowFilterModal(false); }; // Hàm load more data khi scroll đến cuối const handleLoadMore = useCallback(() => { // Không load more nếu: // - Đang loading (ban đầu hoặc loadMore) // - Không còn data để load // - Chưa có data nào (tránh FlatList tự trigger khi list rỗng) if (isLoadingMore || loading || !hasMore || allTrips.length === 0) { return; } setIsLoadingMore(true); const newOffset = payloadTrips.offset + payloadTrips.limit; const updatedPayload = { ...payloadTrips, offset: newOffset, }; setPayloadTrips(updatedPayload); getTripsList(updatedPayload); }, [isLoadingMore, loading, hasMore, allTrips.length, payloadTrips]); const handleViewTrip = (tripId: string) => { // Navigate to trip detail page - chỉ truyền tripId router.push({ pathname: "/trip-detail", params: { tripId }, }); }; const handleEditTrip = (tripId: string) => { // Find the trip from allTrips const tripToEdit = allTrips.find((trip) => trip.id === tripId); if (tripToEdit) { setEditingTrip(tripToEdit); setShowTripFormModal(true); } }; const handleViewTeam = (tripId: string) => { const trip = allTrips.find((t) => t.id === tripId); if (trip) { router.push({ pathname: "/trip-crew", params: { tripId: trip.id, tripName: trip.name || "", tripStatus: String(trip.trip_status ?? ""), // trip_status là số }, }); } }; const handleSendTrip = useCallback( async (tripId: string) => { try { // Import dynamically để tránh circular dependency const { tripApproveRequest } = await import( "@/controller/TripController" ); const { queryTripCrew } = await import( "@/controller/TripCrewController" ); // Kiểm tra xem có thuyền viên không trước khi gửi phê duyệt const crewResponse = await queryTripCrew(tripId); const tripCrews = crewResponse.data?.trip_crews || []; // Nếu response 204 hoặc không có thuyền viên if (crewResponse.status === 204 || tripCrews.length === 0) { Alert.alert( t("diary.noCrewErrorTitle"), t("diary.noCrewErrorMessage") ); return; } // Gọi API gửi yêu cầu phê duyệt await tripApproveRequest(tripId); console.log("✅ Send trip for approval:", tripId); // Reload danh sách để cập nhật trạng thái isInitialLoad.current = true; setAllTrips([]); setHasMore(true); const resetPayload: Model.TripListBody = { ...payloadTrips, offset: 0, }; setPayloadTrips(resetPayload); getTripsList(resetPayload); // Scroll lên đầu setTimeout(() => { flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); }, 100); } catch (error) { console.error("❌ Error sending trip for approval:", error); Alert.alert( t("common.error") || "Lỗi", t("diary.sendApprovalError") || "Không thể gửi yêu cầu phê duyệt. Vui lòng thử lại." ); } }, [payloadTrips, getTripsList, t] ); const handleDeleteTrip = useCallback( (tripId: string) => { Alert.alert( t("diary.cancelTripConfirmTitle") || "Xác nhận hủy chuyến đi", t("diary.cancelTripConfirmMessage") || "Bạn có chắc chắn muốn hủy chuyến đi này?", [ { text: t("common.cancel") || "Hủy", style: "cancel", }, { text: t("common.confirm") || "Xác nhận", style: "destructive", onPress: async () => { try { // Import dynamically để tránh circular dependency const { tripCancelRequest } = await import( "@/controller/TripController" ); // Gọi API hủy chuyến đi await tripCancelRequest(tripId); console.log("✅ Trip cancelled:", tripId); // Reload danh sách để cập nhật trạng thái isInitialLoad.current = true; setAllTrips([]); setHasMore(true); const resetPayload: Model.TripListBody = { ...payloadTrips, offset: 0, }; setPayloadTrips(resetPayload); getTripsList(resetPayload); // Scroll lên đầu setTimeout(() => { flatListRef.current?.scrollToOffset({ offset: 0, animated: true, }); }, 100); } catch (error) { console.error("❌ Error cancelling trip:", error); Alert.alert( t("common.error") || "Lỗi", t("diary.cancelTripError") || "Không thể hủy chuyến đi. Vui lòng thử lại." ); } }, }, ] ); }, [payloadTrips, getTripsList, t] ); // Handle sau khi thêm chuyến đi thành công const handleTripAddSuccess = useCallback(() => { // Reset về trang đầu và gọi lại API isInitialLoad.current = true; setAllTrips([]); setHasMore(true); const resetPayload: Model.TripListBody = { ...payloadTrips, offset: 0, }; setPayloadTrips(resetPayload); getTripsList(resetPayload); // Scroll FlatList lên đầu setTimeout(() => { flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); }, 100); }, [payloadTrips, getTripsList]); // Handle refresh - pull-to-refresh const handleRefresh = useCallback(() => { setRefreshing(true); isInitialLoad.current = true; setAllTrips([]); setHasMore(true); const resetPayload: Model.TripListBody = { ...payloadTrips, offset: 0, }; setPayloadTrips(resetPayload); getTripsList(resetPayload).finally(() => { setRefreshing(false); }); }, [payloadTrips, getTripsList]); // Dynamic styles based on theme const themedStyles = { safeArea: { backgroundColor: colors.background, }, titleText: { color: colors.text, }, countText: { color: colors.textSecondary, }, addButton: { backgroundColor: colors.primary, }, emptyText: { color: colors.textSecondary, }, }; // Render mỗi item trong FlatList const renderTripItem = useCallback( ({ item }: { item: any }) => ( handleViewTrip(item.id)} onEdit={() => handleEditTrip(item.id)} onTeam={() => handleViewTeam(item.id)} onSend={() => handleSendTrip(item.id)} onDelete={() => handleDeleteTrip(item.id)} /> ), [ handleViewTrip, handleEditTrip, handleViewTeam, handleSendTrip, handleDeleteTrip, ] ); // Key extractor cho FlatList const keyExtractor = useCallback((item: any) => item.id, []); // Loading indicator khi load more const renderFooter = () => { // Không hiển thị loading footer nếu không có dữ liệu hoặc không đang load more if (!isLoadingMore || allTrips.length === 0) return null; return ( {t("diary.loadingMore")} ); }; // Empty component const renderEmpty = () => { // Hiển thị loading khi đang load (lần đầu hoặc load more) và chưa có dữ liệu if (loading && allTrips.length === 0) { return ( ); } // Chỉ hiển thị "không có dữ liệu" khi đã load xong và thực sự không có trips return ( {t("diary.noTripsFound")} ); }; return ( {/* Header */} {t("diary.title")} {/* Filter & Add Button Row */} setShowTripFormModal(true)} activeOpacity={0.7} > {t("diary.addTrip")} {/* Trip Count */} {t("diary.tripListCount", { count: tripsList?.total || 0 })} {/* Trip List with FlatList */} {/* Filter Modal */} setShowFilterModal(false)} onApply={handleApplyFilters} /> {/* Add/Edit Trip Modal */} { setShowTripFormModal(false); setEditingTrip(null); }} onSuccess={handleTripAddSuccess} mode={editingTrip ? "edit" : "add"} tripData={editingTrip || undefined} /> ); } const styles = StyleSheet.create({ safeArea: { flex: 1, }, container: { flex: 1, padding: 10, }, titleText: { fontSize: 28, fontWeight: "700", lineHeight: 36, fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System", }), }, headerRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 10, }, actionRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", gap: 12, marginBottom: 12, }, countText: { fontSize: 16, fontWeight: "600", fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System", }), marginBottom: 10, }, addButton: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, height: 40, borderRadius: 8, gap: 6, }, addButtonText: { fontSize: 14, fontWeight: "600", color: "#FFFFFF", fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System", }), }, scrollContent: { paddingBottom: 20, }, emptyState: { alignItems: "center", justifyContent: "center", paddingVertical: 60, }, emptyText: { fontSize: 16, fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System", }), }, loadingFooter: { paddingVertical: 20, alignItems: "center", justifyContent: "center", flexDirection: "row", gap: 10, }, loadingText: { fontSize: 14, fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System", }), }, });