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:
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
@@ -21,9 +22,10 @@ export default function TripCrewPage() {
|
||||
const { t } = useI18n();
|
||||
const { colors } = useThemeContext();
|
||||
const router = useRouter();
|
||||
const { tripId, tripName } = useLocalSearchParams<{
|
||||
const { tripId, tripName, tripStatus } = useLocalSearchParams<{
|
||||
tripId: string;
|
||||
tripName?: string;
|
||||
tripStatus?: string;
|
||||
}>();
|
||||
|
||||
// State
|
||||
@@ -104,9 +106,8 @@ export default function TripCrewPage() {
|
||||
);
|
||||
};
|
||||
|
||||
// Save crew handler
|
||||
const handleSaveCrew = async (formData: any) => {
|
||||
// TODO: Call API to add/edit crew when available
|
||||
// Save crew handler - API được gọi trong AddEditCrewModal, sau đó refresh danh sách
|
||||
const handleSaveCrew = async () => {
|
||||
await fetchCrewData();
|
||||
};
|
||||
|
||||
@@ -146,13 +147,19 @@ export default function TripCrewPage() {
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<TouchableOpacity onPress={handleAddCrew} style={styles.addButton}>
|
||||
<Ionicons name="add" size={24} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
{tripStatus === "0" && (
|
||||
<TouchableOpacity onPress={handleAddCrew} style={styles.addButton}>
|
||||
<Ionicons name="add" size={24} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<ScrollView
|
||||
style={styles.content}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
@@ -186,7 +193,7 @@ export default function TripCrewPage() {
|
||||
onDelete={handleDeleteCrew}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* Footer - Crew count */}
|
||||
<View
|
||||
@@ -224,10 +231,11 @@ export default function TripCrewPage() {
|
||||
address: editingCrew.Person?.address || "",
|
||||
role: editingCrew.role || "crew",
|
||||
// Note lấy từ trip (ghi chú chuyến đi), fallback về note từ Person
|
||||
note: editingCrew.note || editingCrew.Person?.note || "",
|
||||
note: editingCrew.note || "",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
tripStatus={tripStatus ? Number(tripStatus) : undefined}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -275,7 +283,10 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
padding: 16,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
convertFishingGears,
|
||||
convertTripCosts,
|
||||
} from "@/utils/tripDataConverters";
|
||||
import { queryAlarms } from "@/controller/AlarmController";
|
||||
import { queryTripCrew } from "@/controller/TripCrewController";
|
||||
import { queryTripById } from "@/controller/TripController";
|
||||
|
||||
// Reuse existing components
|
||||
import CrewList from "@/components/diary/TripCrewModal/CrewList";
|
||||
@@ -36,28 +39,77 @@ export default function TripDetailPage() {
|
||||
const { t } = useI18n();
|
||||
const { colors } = useThemeContext();
|
||||
const router = useRouter();
|
||||
const { tripId, tripData: tripDataParam } = useLocalSearchParams<{
|
||||
const { tripId } = useLocalSearchParams<{
|
||||
tripId: string;
|
||||
tripData?: string;
|
||||
}>();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [trip, setTrip] = useState<Model.Trip | null>(null);
|
||||
const [alerts] = useState<Model.Alarm[]>([]); // TODO: Fetch from API
|
||||
const [alerts, setAlerts] = useState<Model.Alarm[]>([]);
|
||||
const [crews, setCrews] = useState<Model.TripCrews[]>([]);
|
||||
|
||||
// Parse trip data from params or fetch from API
|
||||
// Fetch trip data từ API
|
||||
useEffect(() => {
|
||||
if (tripDataParam) {
|
||||
const fetchTripData = async () => {
|
||||
if (!tripId) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const parsedTrip = JSON.parse(tripDataParam) as Model.Trip;
|
||||
setTrip(parsedTrip);
|
||||
} catch (e) {
|
||||
console.error("Error parsing trip data:", e);
|
||||
const response = await queryTripById(tripId);
|
||||
if (response.data) {
|
||||
setTrip(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Lỗi khi tải thông tin chuyến đi:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
// TODO: Fetch trip detail from API using tripId if not passed via params
|
||||
setLoading(false);
|
||||
}, [tripDataParam, tripId]);
|
||||
};
|
||||
|
||||
fetchTripData();
|
||||
}, [tripId]);
|
||||
|
||||
// Fetch alarms cho chuyến đi dựa trên thing_id (vms_id)
|
||||
useEffect(() => {
|
||||
const fetchAlarms = async () => {
|
||||
if (!trip?.vms_id) return;
|
||||
|
||||
try {
|
||||
const response = await queryAlarms({
|
||||
offset: 0,
|
||||
limit: 100,
|
||||
order: "time",
|
||||
dir: "desc",
|
||||
thing_id: trip.vms_id,
|
||||
});
|
||||
|
||||
if (response.data?.alarms) {
|
||||
setAlerts(response.data.alarms);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Lỗi khi tải alarms:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAlarms();
|
||||
}, [trip?.vms_id]);
|
||||
|
||||
// Fetch danh sách thuyền viên
|
||||
useEffect(() => {
|
||||
const fetchCrews = async () => {
|
||||
if (!tripId) return;
|
||||
|
||||
try {
|
||||
const response = await queryTripCrew(tripId);
|
||||
// API trả về { trip_crews: [...] }
|
||||
if (response.data?.trip_crews) {
|
||||
setCrews(response.data.trip_crews);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
fetchCrews();
|
||||
}, [tripId]);
|
||||
|
||||
// Convert trip data to component format using memoization
|
||||
const fishingGears = useMemo(
|
||||
@@ -72,11 +124,20 @@ export default function TripDetailPage() {
|
||||
|
||||
const statusConfig = useMemo(() => {
|
||||
const status = trip?.trip_status ?? 0;
|
||||
return TRIP_STATUS_CONFIG[status as keyof typeof TRIP_STATUS_CONFIG] || TRIP_STATUS_CONFIG[0];
|
||||
return (
|
||||
TRIP_STATUS_CONFIG[status as keyof typeof TRIP_STATUS_CONFIG] ||
|
||||
TRIP_STATUS_CONFIG[0]
|
||||
);
|
||||
}, [trip?.trip_status]);
|
||||
|
||||
// Empty section component
|
||||
const EmptySection = ({ icon, message }: { icon: string; message: string }) => (
|
||||
const EmptySection = ({
|
||||
icon,
|
||||
message,
|
||||
}: {
|
||||
icon: string;
|
||||
message: string;
|
||||
}) => (
|
||||
<View style={styles.emptySection}>
|
||||
<Ionicons name={icon as any} size={40} color={colors.textSecondary} />
|
||||
<Text style={[styles.emptyText, { color: colors.textSecondary }]}>
|
||||
@@ -88,7 +149,10 @@ export default function TripDetailPage() {
|
||||
// Render loading state
|
||||
if (loading) {
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]} edges={["top"]}>
|
||||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
edges={["top"]}
|
||||
>
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
<Text style={[styles.loadingText, { color: colors.textSecondary }]}>
|
||||
@@ -102,10 +166,21 @@ export default function TripDetailPage() {
|
||||
// Render error state
|
||||
if (!trip) {
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]} edges={["top"]}>
|
||||
<Header title={t("diary.tripDetail.title")} onBack={() => router.back()} colors={colors} />
|
||||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
edges={["top"]}
|
||||
>
|
||||
<Header
|
||||
title={t("diary.tripDetail.title")}
|
||||
onBack={() => router.back()}
|
||||
colors={colors}
|
||||
/>
|
||||
<View style={styles.errorContainer}>
|
||||
<Ionicons name="alert-circle-outline" size={48} color={colors.error || "#FF3B30"} />
|
||||
<Ionicons
|
||||
name="alert-circle-outline"
|
||||
size={48}
|
||||
color={colors.error || "#FF3B30"}
|
||||
/>
|
||||
<Text style={[styles.errorText, { color: colors.textSecondary }]}>
|
||||
{t("diary.tripDetail.notFound")}
|
||||
</Text>
|
||||
@@ -115,19 +190,44 @@ export default function TripDetailPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]} edges={["top"]}>
|
||||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
edges={["top"]}
|
||||
>
|
||||
{/* Header with status badge */}
|
||||
<View style={[styles.header, { backgroundColor: colors.card, borderBottomColor: colors.separator }]}>
|
||||
<TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
|
||||
<View
|
||||
style={[
|
||||
styles.header,
|
||||
{ backgroundColor: colors.card, borderBottomColor: colors.separator },
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => router.back()}
|
||||
style={styles.backButton}
|
||||
>
|
||||
<Ionicons name="arrow-back" size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
<View style={styles.headerTitles}>
|
||||
<Text style={[styles.title, { color: colors.text }]} numberOfLines={1}>
|
||||
<Text
|
||||
style={[styles.title, { color: colors.text }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{trip.name || t("diary.tripDetail.title")}
|
||||
</Text>
|
||||
<View style={[styles.statusBadge, { backgroundColor: statusConfig.bgColor }]}>
|
||||
<Ionicons name={statusConfig.icon as any} size={12} color={statusConfig.textColor} />
|
||||
<Text style={[styles.statusText, { color: statusConfig.textColor }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.statusBadge,
|
||||
{ backgroundColor: statusConfig.bgColor },
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name={statusConfig.icon as any}
|
||||
size={12}
|
||||
color={statusConfig.textColor}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.statusText, { color: statusConfig.textColor }]}
|
||||
>
|
||||
{statusConfig.label}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -157,10 +257,18 @@ export default function TripDetailPage() {
|
||||
>
|
||||
{tripCosts.length > 0 ? (
|
||||
<View style={styles.sectionInnerContent}>
|
||||
<MaterialCostList items={tripCosts} onChange={() => {}} disabled hideTitle />
|
||||
<MaterialCostList
|
||||
items={tripCosts}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
hideTitle
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<EmptySection icon="receipt-outline" message={t("diary.tripDetail.noCosts")} />
|
||||
<EmptySection
|
||||
icon="receipt-outline"
|
||||
message={t("diary.tripDetail.noCosts")}
|
||||
/>
|
||||
)}
|
||||
</SectionCard>
|
||||
|
||||
@@ -174,10 +282,18 @@ export default function TripDetailPage() {
|
||||
>
|
||||
{fishingGears.length > 0 ? (
|
||||
<View style={styles.sectionInnerContent}>
|
||||
<FishingGearList items={fishingGears} onChange={() => {}} disabled hideTitle />
|
||||
<FishingGearList
|
||||
items={fishingGears}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
hideTitle
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<EmptySection icon="build-outline" message={t("diary.tripDetail.noGears")} />
|
||||
<EmptySection
|
||||
icon="build-outline"
|
||||
message={t("diary.tripDetail.noGears")}
|
||||
/>
|
||||
)}
|
||||
</SectionCard>
|
||||
|
||||
@@ -185,14 +301,17 @@ export default function TripDetailPage() {
|
||||
<SectionCard
|
||||
title={t("diary.tripDetail.crew")}
|
||||
icon="people-outline"
|
||||
count={trip.crews?.length || 0}
|
||||
count={crews.length}
|
||||
collapsible
|
||||
defaultExpanded
|
||||
>
|
||||
{trip.crews && trip.crews.length > 0 ? (
|
||||
<CrewList crews={trip.crews} />
|
||||
{crews.length > 0 ? (
|
||||
<CrewList crews={crews} />
|
||||
) : (
|
||||
<EmptySection icon="person-add-outline" message={t("diary.tripDetail.noCrew")} />
|
||||
<EmptySection
|
||||
icon="person-add-outline"
|
||||
message={t("diary.tripDetail.noCrew")}
|
||||
/>
|
||||
)}
|
||||
</SectionCard>
|
||||
|
||||
@@ -214,7 +333,12 @@ function Header({
|
||||
colors: any;
|
||||
}) {
|
||||
return (
|
||||
<View style={[styles.header, { backgroundColor: colors.card, borderBottomColor: colors.separator }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.header,
|
||||
{ backgroundColor: colors.card, borderBottomColor: colors.separator },
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
||||
<Ionicons name="arrow-back" size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
@@ -238,7 +362,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
loadingText: {
|
||||
fontSize: 14,
|
||||
fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System" }),
|
||||
fontFamily: Platform.select({
|
||||
ios: "System",
|
||||
android: "Roboto",
|
||||
default: "System",
|
||||
}),
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
@@ -259,7 +387,11 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: "700",
|
||||
fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System" }),
|
||||
fontFamily: Platform.select({
|
||||
ios: "System",
|
||||
android: "Roboto",
|
||||
default: "System",
|
||||
}),
|
||||
},
|
||||
statusBadge: {
|
||||
flexDirection: "row",
|
||||
@@ -272,7 +404,11 @@ const styles = StyleSheet.create({
|
||||
statusText: {
|
||||
fontSize: 11,
|
||||
fontWeight: "600",
|
||||
fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System" }),
|
||||
fontFamily: Platform.select({
|
||||
ios: "System",
|
||||
android: "Roboto",
|
||||
default: "System",
|
||||
}),
|
||||
},
|
||||
placeholder: {
|
||||
width: 32,
|
||||
@@ -294,7 +430,11 @@ const styles = StyleSheet.create({
|
||||
errorText: {
|
||||
fontSize: 14,
|
||||
textAlign: "center",
|
||||
fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System" }),
|
||||
fontFamily: Platform.select({
|
||||
ios: "System",
|
||||
android: "Roboto",
|
||||
default: "System",
|
||||
}),
|
||||
},
|
||||
sectionInnerContent: {
|
||||
marginTop: 5,
|
||||
@@ -307,6 +447,10 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 14,
|
||||
fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System" }),
|
||||
fontFamily: Platform.select({
|
||||
ios: "System",
|
||||
android: "Roboto",
|
||||
default: "System",
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user