thêm tab "Xem chi tiết chuyến đi", "Xem chi tiết thành viên chuyến đi", tái sử dụng lại components modal tripForm

This commit is contained in:
2025-12-23 23:10:19 +07:00
parent afc6acbfe2
commit 000a4ed856
22 changed files with 3221 additions and 379 deletions

View File

@@ -2,15 +2,14 @@ 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 { queryProfile } from "@/controller/AuthController";
import { useI18n } from "@/hooks/use-i18n";
import { useColorScheme } from "@/hooks/use-theme-context";
import { useThemeContext } from "@/hooks/use-theme-context";
import { addUserStorage } from "@/utils/storage";
import { useEffect, useRef } from "react";
export default function TabLayout() {
const colorScheme = useColorScheme();
const { colors, colorScheme } = useThemeContext();
const segments = useSegments() as string[];
const prev = useRef<string | null>(null);
const currentSegment = segments[1] ?? segments[segments.length - 1] ?? null;
@@ -51,9 +50,18 @@ export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
tabBarActiveTintColor: colors.tint,
headerShown: false,
tabBarButton: HapticTab,
// Set tab bar styles based on theme - prevents white flash
tabBarStyle: {
backgroundColor: colors.background,
borderTopColor: colors.separator,
},
// Set screen content background
sceneStyle: {
backgroundColor: colors.background,
},
}}
>
<Tabs.Screen

View File

@@ -10,6 +10,7 @@ import {
} 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";
@@ -23,10 +24,10 @@ import { useThemeContext } from "@/hooks/use-theme-context";
export default function diary() {
const { t } = useI18n();
const { colors } = useThemeContext();
const router = useRouter();
const [showFilterModal, setShowFilterModal] = useState(false);
const [showAddTripModal, setShowAddTripModal] = useState(false);
const [editingTrip, setEditingTrip] = useState<Model.Trip | null>(null);
const [viewingTrip, setViewingTrip] = useState<Model.Trip | null>(null);
const [filters, setFilters] = useState<FilterValues>({
status: null,
startDate: null,
@@ -40,6 +41,10 @@ export default function diary() {
const [hasMore, setHasMore] = useState(true);
const isInitialLoad = useRef(true);
const flatListRef = useRef<FlatList>(null);
// Refs to prevent duplicate API calls from React Compiler/Strict Mode
const hasInitializedThings = useRef(false);
const hasInitializedTrips = useRef(false);
// Body call API things (đang fix cứng)
const payloadThings: Model.SearchThingBody = {
@@ -55,6 +60,8 @@ export default function diary() {
// Gọi API things
const { getThings } = useThings();
useEffect(() => {
if (hasInitializedThings.current) return;
hasInitializedThings.current = true;
getThings(payloadThings);
}, []);
@@ -79,10 +86,10 @@ export default function diary() {
const { tripsList, getTripsList, loading } = useTripsList();
// console.log("Payload trips:", payloadTrips);
// Gọi API trips lần đầu
useEffect(() => {
if (hasInitializedTrips.current) return;
hasInitializedTrips.current = true;
isInitialLoad.current = true;
setAllTrips([]);
setHasMore(true);
@@ -157,7 +164,11 @@ export default function diary() {
// Hàm load more data khi scroll đến cuối
const handleLoadMore = useCallback(() => {
if (isLoadingMore || !hasMore) {
// 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;
}
@@ -169,7 +180,7 @@ export default function diary() {
};
setPayloadTrips(updatedPayload);
getTripsList(updatedPayload);
}, [isLoadingMore, hasMore, payloadTrips]);
}, [isLoadingMore, loading, hasMore, allTrips.length, payloadTrips]);
// const handleTripPress = (tripId: string) => {
// // TODO: Navigate to trip detail
@@ -177,11 +188,16 @@ export default function diary() {
// };
const handleViewTrip = (tripId: string) => {
// Find the trip from allTrips and open modal in view mode
// Navigate to trip detail page instead of opening modal
const tripToView = allTrips.find((trip) => trip.id === tripId);
if (tripToView) {
setViewingTrip(tripToView);
setShowAddTripModal(true);
router.push({
pathname: "/trip-detail",
params: {
tripId: tripToView.id,
tripData: JSON.stringify(tripToView)
},
});
}
};
@@ -195,8 +211,13 @@ export default function diary() {
};
const handleViewTeam = (tripId: string) => {
console.log("View team:", tripId);
// TODO: Navigate to team management
const trip = allTrips.find((t) => t.id === tripId);
if (trip) {
router.push({
pathname: "/trip-crew",
params: { tripId: trip.id, tripName: trip.name || "" },
});
}
};
const handleSendTrip = (tripId: string) => {
@@ -260,7 +281,13 @@ export default function diary() {
onDelete={() => handleDeleteTrip(item.id)}
/>
),
[handleViewTrip, handleEditTrip, handleViewTeam, handleSendTrip, handleDeleteTrip]
[
handleViewTrip,
handleEditTrip,
handleViewTeam,
handleSendTrip,
handleDeleteTrip,
]
);
// Key extractor cho FlatList
@@ -301,10 +328,15 @@ export default function diary() {
};
return (
<SafeAreaView style={[styles.safeArea, themedStyles.safeArea]} edges={["top"]}>
<SafeAreaView
style={[styles.safeArea, themedStyles.safeArea]}
edges={["top"]}
>
<View style={styles.container}>
{/* Header */}
<Text style={[styles.titleText, themedStyles.titleText]}>{t("diary.title")}</Text>
<Text style={[styles.titleText, themedStyles.titleText]}>
{t("diary.title")}
</Text>
{/* Filter & Add Button Row */}
<View style={styles.actionRow}>
@@ -358,17 +390,16 @@ export default function diary() {
onApply={handleApplyFilters}
/>
{/* Add/Edit/View Trip Modal */}
{/* Add/Edit Trip Modal */}
<AddTripModal
visible={showAddTripModal}
onClose={() => {
setShowAddTripModal(false);
setEditingTrip(null);
setViewingTrip(null);
}}
onSuccess={handleTripAddSuccess}
mode={viewingTrip ? 'view' : editingTrip ? 'edit' : 'add'}
tripData={viewingTrip || editingTrip || undefined}
mode={editingTrip ? "edit" : "add"}
tripData={editingTrip || undefined}
/>
</SafeAreaView>
);
@@ -394,17 +425,10 @@ const styles = StyleSheet.create({
}),
},
actionRow: {
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
gap: 12,
marginBottom: 12,
},
headerRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginTop: 20,
gap: 12,
marginBottom: 12,
},
countText: {
@@ -421,7 +445,7 @@ const styles = StyleSheet.create({
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 8,
height: 40,
borderRadius: 8,
gap: 6,
},