Cập nhật tab Nhật ký ( CRUD chuyến đi, CRUD thuyền viên trong chuyến đi )

This commit is contained in:
2025-12-29 15:56:47 +07:00
parent 190e44b09e
commit 871360af49
24 changed files with 1451 additions and 407 deletions

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import {
ActivityIndicator,
Alert,
FlatList,
Platform,
StyleSheet,
@@ -20,6 +21,7 @@ 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();
@@ -42,10 +44,6 @@ export default function diary() {
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 = {
offset: 0,
@@ -57,13 +55,21 @@ export default function diary() {
},
};
// Gọi API things
const { getThings } = useThings();
// Gọi API things nếu chưa có dữ liệu
const { things, getThings } = useThings();
useEffect(() => {
if (hasInitializedThings.current) return;
hasInitializedThings.current = true;
getThings(payloadThings);
}, []);
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<Model.TripListBody>({
@@ -88,9 +94,8 @@ export default function diary() {
// Gọi API trips lần đầu
useEffect(() => {
if (hasInitializedTrips.current) return;
hasInitializedTrips.current = true;
isInitialLoad.current = true;
if (!isInitialLoad.current) return;
isInitialLoad.current = false;
setAllTrips([]);
setHasMore(true);
getTripsList(payloadTrips);
@@ -182,23 +187,12 @@ export default function diary() {
getTripsList(updatedPayload);
}, [isLoadingMore, loading, hasMore, allTrips.length, payloadTrips]);
// const handleTripPress = (tripId: string) => {
// // TODO: Navigate to trip detail
// console.log("Trip pressed:", tripId);
// };
const handleViewTrip = (tripId: string) => {
// Navigate to trip detail page instead of opening modal
const tripToView = allTrips.find((trip) => trip.id === tripId);
if (tripToView) {
router.push({
pathname: "/trip-detail",
params: {
tripId: tripToView.id,
tripData: JSON.stringify(tripToView),
},
});
}
// Navigate to trip detail page - chỉ truyền tripId
router.push({
pathname: "/trip-detail",
params: { tripId },
});
};
const handleEditTrip = (tripId: string) => {
@@ -215,20 +209,107 @@ export default function diary() {
if (trip) {
router.push({
pathname: "/trip-crew",
params: { tripId: trip.id, tripName: trip.name || "" },
params: {
tripId: trip.id,
tripName: trip.name || "",
tripStatus: String(trip.trip_status ?? ""), // trip_status là số
},
});
}
};
const handleSendTrip = (tripId: string) => {
console.log("Send trip:", tripId);
// TODO: Send trip for approval
};
const handleSendTrip = useCallback(
async (tripId: string) => {
try {
// Import dynamically để tránh circular dependency
const { tripApproveRequest } = await import(
"@/controller/TripController"
);
const handleDeleteTrip = (tripId: string) => {
console.log("Delete trip:", tripId);
// TODO: Show confirmation dialog and delete trip
};
// 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);
}
},
[payloadTrips, getTripsList]
);
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(() => {
@@ -249,6 +330,24 @@ export default function diary() {
}, 100);
}, [payloadTrips, getTripsList]);
// Handle reload - gọi lại API
const handleReload = useCallback(() => {
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]);
// Dynamic styles based on theme
const themedStyles = {
safeArea: {
@@ -273,7 +372,6 @@ export default function diary() {
({ item }: { item: any }) => (
<TripCard
trip={item}
// onPress={() => handleTripPress(item.id)}
onView={() => handleViewTrip(item.id)}
onEdit={() => handleEditTrip(item.id)}
onTeam={() => handleViewTeam(item.id)}
@@ -334,9 +432,26 @@ export default function diary() {
>
<View style={styles.container}>
{/* Header */}
<Text style={[styles.titleText, themedStyles.titleText]}>
{t("diary.title")}
</Text>
<View style={styles.headerRow}>
<Text style={[styles.titleText, themedStyles.titleText]}>
{t("diary.title")}
</Text>
<TouchableOpacity
style={[
styles.reloadButton,
{ backgroundColor: colors.backgroundSecondary },
]}
onPress={handleReload}
activeOpacity={0.7}
disabled={loading}
>
{loading && allTrips.length === 0 ? (
<ActivityIndicator size="small" color={colors.primary} />
) : (
<Ionicons name="reload" size={20} color={colors.primary} />
)}
</TouchableOpacity>
</View>
{/* Filter & Add Button Row */}
<View style={styles.actionRow}>
@@ -417,13 +532,25 @@ const styles = StyleSheet.create({
fontSize: 28,
fontWeight: "700",
lineHeight: 36,
marginBottom: 10,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
headerRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
},
reloadButton: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: "center",
alignItems: "center",
},
actionRow: {
flexDirection: "row",
justifyContent: "space-between",

View File

@@ -211,7 +211,7 @@ export default function HomeScreen() {
// console.log("No ZoneApproachingAlarm");
}
if (entered.length > 0) {
console.log("ZoneEnteredAlarm: ", entered);
// console.log("ZoneEnteredAlarm: ", entered);
} else {
// console.log("No ZoneEnteredAlarm");
}