Gip khi cuộn xuống ( tab diary )
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
@@ -29,6 +30,12 @@ export default function diary() {
|
||||
selectedShip: null, // Tàu được chọn
|
||||
});
|
||||
|
||||
// State for pagination
|
||||
const [allTrips, setAllTrips] = useState<any[]>([]);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const isInitialLoad = useRef(true);
|
||||
|
||||
// Body call API things (đang fix cứng)
|
||||
const payloadThings: Model.SearchThingBody = {
|
||||
offset: 0,
|
||||
@@ -65,18 +72,48 @@ export default function diary() {
|
||||
},
|
||||
});
|
||||
|
||||
const { tripsList, getTripsList } = useTripsList();
|
||||
const { tripsList, getTripsList, loading } = useTripsList();
|
||||
|
||||
// console.log("Payload trips:", payloadTrips);
|
||||
|
||||
// Gọi API trips lần đầu
|
||||
useEffect(() => {
|
||||
isInitialLoad.current = true;
|
||||
setAllTrips([]);
|
||||
setHasMore(true);
|
||||
getTripsList(payloadTrips);
|
||||
}, []);
|
||||
|
||||
// Gọi lại API khi payload thay đổi (do filter)
|
||||
// Xử lý khi nhận được dữ liệu mới từ API
|
||||
useEffect(() => {
|
||||
getTripsList(payloadTrips);
|
||||
console.log("Payload trips:", payloadTrips);
|
||||
}, [payloadTrips]);
|
||||
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);
|
||||
@@ -85,10 +122,11 @@ export default function diary() {
|
||||
const handleApplyFilters = (newFilters: FilterValues) => {
|
||||
setFilters(newFilters);
|
||||
|
||||
// Cập nhật payload với filter mới
|
||||
// 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
|
||||
@@ -104,10 +142,30 @@ export default function diary() {
|
||||
},
|
||||
};
|
||||
|
||||
// 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(() => {
|
||||
if (isLoadingMore || !hasMore) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingMore(true);
|
||||
const newOffset = payloadTrips.offset + payloadTrips.limit;
|
||||
const updatedPayload = {
|
||||
...payloadTrips,
|
||||
offset: newOffset,
|
||||
};
|
||||
setPayloadTrips(updatedPayload);
|
||||
getTripsList(updatedPayload);
|
||||
}, [isLoadingMore, hasMore, payloadTrips]);
|
||||
|
||||
const handleTripPress = (tripId: string) => {
|
||||
// TODO: Navigate to trip detail
|
||||
console.log("Trip pressed:", tripId);
|
||||
@@ -157,6 +215,59 @@ export default function diary() {
|
||||
},
|
||||
};
|
||||
|
||||
// Render mỗi item trong FlatList
|
||||
const renderTripItem = useCallback(
|
||||
({ item }: { item: any }) => (
|
||||
<TripCard
|
||||
trip={item}
|
||||
onPress={() => handleTripPress(item.id)}
|
||||
onView={() => handleViewTrip(item.id)}
|
||||
onEdit={() => handleEditTrip(item.id)}
|
||||
onTeam={() => handleViewTeam(item.id)}
|
||||
onSend={() => handleSendTrip(item.id)}
|
||||
onDelete={() => handleDeleteTrip(item.id)}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// 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 (
|
||||
<View style={styles.loadingFooter}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
<Text style={[styles.loadingText, { color: colors.textSecondary }]}>
|
||||
{t("diary.loadingMore")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<View style={styles.emptyState}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Chỉ hiển thị "không có dữ liệu" khi đã load xong và thực sự không có trips
|
||||
return (
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={[styles.emptyText, themedStyles.emptyText]}>
|
||||
{t("diary.noTripsFound")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.safeArea, themedStyles.safeArea]} edges={["top"]}>
|
||||
<View style={styles.container}>
|
||||
@@ -189,33 +300,22 @@ export default function diary() {
|
||||
{t("diary.tripListCount", { count: tripsList?.total || 0 })}
|
||||
</Text>
|
||||
|
||||
{/* Trip List */}
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
{/* Trip List with FlatList */}
|
||||
<FlatList
|
||||
data={allTrips}
|
||||
renderItem={renderTripItem}
|
||||
keyExtractor={keyExtractor}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{tripsList?.trips?.map((trip) => (
|
||||
<TripCard
|
||||
key={trip.id}
|
||||
trip={trip}
|
||||
onPress={() => handleTripPress(trip.id)}
|
||||
onView={() => handleViewTrip(trip.id)}
|
||||
onEdit={() => handleEditTrip(trip.id)}
|
||||
onTeam={() => handleViewTeam(trip.id)}
|
||||
onSend={() => handleSendTrip(trip.id)}
|
||||
onDelete={() => handleDeleteTrip(trip.id)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{(!tripsList || !tripsList.trips || tripsList.trips.length === 0) && (
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={[styles.emptyText, themedStyles.emptyText]}>
|
||||
{t("diary.noTripsFound")}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
onEndReached={handleLoadMore}
|
||||
onEndReachedThreshold={0.5}
|
||||
ListFooterComponent={renderFooter}
|
||||
ListEmptyComponent={renderEmpty}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={10}
|
||||
initialNumToRender={10}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Filter Modal */}
|
||||
@@ -289,9 +389,6 @@ const styles = StyleSheet.create({
|
||||
default: "System",
|
||||
}),
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 20,
|
||||
},
|
||||
@@ -308,4 +405,19 @@ const styles = StyleSheet.create({
|
||||
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",
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user