453 lines
12 KiB
TypeScript
453 lines
12 KiB
TypeScript
import React from "react";
|
|
import { View, Text, StyleSheet, Platform } from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useThemeContext } from "@/hooks/use-theme-context";
|
|
import { useI18n } from "@/hooks/use-i18n";
|
|
import SectionCard from "./SectionCard";
|
|
|
|
interface FishingLogsSectionProps {
|
|
fishingLogs?: Model.FishingLog[] | null;
|
|
}
|
|
|
|
export default function FishingLogsSection({
|
|
fishingLogs = [],
|
|
}: FishingLogsSectionProps) {
|
|
const { t } = useI18n();
|
|
const { colors } = useThemeContext();
|
|
|
|
const logs = fishingLogs || [];
|
|
|
|
const formatDateTime = (date?: Date) => {
|
|
if (!date) return "--";
|
|
const d = new Date(date);
|
|
return d.toLocaleString("vi-VN", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
};
|
|
|
|
const formatCoord = (lat?: number, lon?: number) => {
|
|
if (lat === undefined || lon === undefined) return "--";
|
|
return `${lat.toFixed(4)}°N, ${lon.toFixed(4)}°E`;
|
|
};
|
|
|
|
const getStatusLabel = (status?: number) => {
|
|
switch (status) {
|
|
case 0:
|
|
return {
|
|
label: t("diary.tripDetail.logStatusProcessing"),
|
|
color: "#FEF3C7",
|
|
textColor: "#92400E",
|
|
};
|
|
case 1:
|
|
return {
|
|
label: t("diary.tripDetail.logStatusSuccess"),
|
|
color: "#D1FAE5",
|
|
textColor: "#065F46",
|
|
};
|
|
case 2:
|
|
return {
|
|
label: t("diary.tripDetail.logStatusCancelled"),
|
|
color: "#FEE2E2",
|
|
textColor: "#B91C1C",
|
|
};
|
|
default:
|
|
return {
|
|
label: t("diary.tripDetail.logStatusUnknown"),
|
|
color: "#F3F4F6",
|
|
textColor: "#4B5563",
|
|
};
|
|
}
|
|
};
|
|
|
|
return (
|
|
<SectionCard
|
|
title={t("diary.tripDetail.fishingLogs")}
|
|
icon="boat-outline"
|
|
count={logs.length}
|
|
collapsible
|
|
defaultExpanded={true}
|
|
>
|
|
{logs.length === 0 ? (
|
|
<View style={styles.emptyContainer}>
|
|
<Ionicons
|
|
name="fish-outline"
|
|
size={40}
|
|
color={colors.textSecondary}
|
|
/>
|
|
<Text style={[styles.emptyText, { color: colors.textSecondary }]}>
|
|
{t("diary.tripDetail.noFishingLogs")}
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<View style={styles.list}>
|
|
{logs.map((log, index) => {
|
|
const status = getStatusLabel(log.status);
|
|
const catchCount = log.info?.length || 0;
|
|
|
|
return (
|
|
<View
|
|
key={log.fishing_log_id || index}
|
|
style={[styles.logItem, { borderColor: colors.separator }]}
|
|
>
|
|
{/* Header */}
|
|
<View style={styles.logHeader}>
|
|
<View style={styles.logIndex}>
|
|
<Text
|
|
style={[styles.logIndexText, { color: colors.primary }]}
|
|
>
|
|
#{index + 1}
|
|
</Text>
|
|
</View>
|
|
<View
|
|
style={[
|
|
styles.statusBadge,
|
|
{ backgroundColor: status.color },
|
|
]}
|
|
>
|
|
<Text
|
|
style={[styles.statusText, { color: status.textColor }]}
|
|
>
|
|
{status.label}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Time Info */}
|
|
<View style={styles.timeRow}>
|
|
<View style={styles.timeItem}>
|
|
<Ionicons
|
|
name="play-circle-outline"
|
|
size={16}
|
|
color={colors.success || "#22C55E"}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.timeLabel,
|
|
{ color: colors.textSecondary },
|
|
]}
|
|
>
|
|
{t("diary.tripDetail.startTime")}:
|
|
</Text>
|
|
<Text style={[styles.timeValue, { color: colors.text }]}>
|
|
{formatDateTime(log.start_at)}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.timeItem}>
|
|
<Ionicons
|
|
name="stop-circle-outline"
|
|
size={16}
|
|
color={colors.error || "#EF4444"}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.timeLabel,
|
|
{ color: colors.textSecondary },
|
|
]}
|
|
>
|
|
{t("diary.tripDetail.endTime")}:
|
|
</Text>
|
|
<Text style={[styles.timeValue, { color: colors.text }]}>
|
|
{formatDateTime(log.end_at)}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Location Info */}
|
|
<View
|
|
style={[
|
|
styles.locationContainer,
|
|
{ backgroundColor: colors.backgroundSecondary },
|
|
]}
|
|
>
|
|
<View style={styles.locationItem}>
|
|
<Ionicons
|
|
name="location"
|
|
size={14}
|
|
color={colors.success || "#22C55E"}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.locationLabel,
|
|
{ color: colors.textSecondary },
|
|
]}
|
|
>
|
|
{t("diary.tripDetail.startLocation")}:
|
|
</Text>
|
|
<Text
|
|
style={[styles.locationValue, { color: colors.text }]}
|
|
>
|
|
{formatCoord(log.start_lat, log.start_lon)}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.locationItem}>
|
|
<Ionicons
|
|
name="location"
|
|
size={14}
|
|
color={colors.error || "#EF4444"}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.locationLabel,
|
|
{ color: colors.textSecondary },
|
|
]}
|
|
>
|
|
{t("diary.tripDetail.haulLocation")}:
|
|
</Text>
|
|
<Text
|
|
style={[styles.locationValue, { color: colors.text }]}
|
|
>
|
|
{formatCoord(log.haul_lat, log.haul_lon)}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Catch Info */}
|
|
{catchCount > 0 && (
|
|
<View style={styles.catchContainer}>
|
|
<View style={styles.catchHeader}>
|
|
<Ionicons name="fish" size={16} color={colors.primary} />
|
|
<Text style={[styles.catchLabel, { color: colors.text }]}>
|
|
{t("diary.tripDetail.catchInfo")} ({catchCount}{" "}
|
|
{t("diary.tripDetail.species")})
|
|
</Text>
|
|
</View>
|
|
<View style={styles.catchList}>
|
|
{log.info?.slice(0, 3).map((fish, fishIndex) => (
|
|
<View key={fishIndex} style={styles.catchItem}>
|
|
<Text
|
|
style={[styles.fishName, { color: colors.text }]}
|
|
>
|
|
{fish.fish_name ||
|
|
t("diary.tripDetail.unknownFish")}
|
|
</Text>
|
|
<Text
|
|
style={[
|
|
styles.fishAmount,
|
|
{ color: colors.textSecondary },
|
|
]}
|
|
>
|
|
{fish.catch_number} {fish.catch_unit}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
{catchCount > 3 && (
|
|
<Text
|
|
style={[styles.moreText, { color: colors.primary }]}
|
|
>
|
|
+{catchCount - 3} {t("diary.tripDetail.more")}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* Weather */}
|
|
{log.weather_description && (
|
|
<View style={styles.weatherRow}>
|
|
<Ionicons
|
|
name="cloudy-outline"
|
|
size={14}
|
|
color={colors.textSecondary}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.weatherText,
|
|
{ color: colors.textSecondary },
|
|
]}
|
|
>
|
|
{log.weather_description}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
)}
|
|
</SectionCard>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
emptyContainer: {
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
paddingVertical: 24,
|
|
gap: 8,
|
|
},
|
|
emptyText: {
|
|
fontSize: 14,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
list: {
|
|
gap: 16,
|
|
},
|
|
logItem: {
|
|
borderWidth: 1,
|
|
borderRadius: 10,
|
|
padding: 12,
|
|
},
|
|
logHeader: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
marginBottom: 10,
|
|
},
|
|
logIndex: {
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
borderRadius: 4,
|
|
backgroundColor: "rgba(59, 130, 246, 0.1)",
|
|
},
|
|
logIndexText: {
|
|
fontSize: 14,
|
|
fontWeight: "700",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
statusBadge: {
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
borderRadius: 4,
|
|
},
|
|
statusText: {
|
|
fontSize: 12,
|
|
fontWeight: "600",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
timeRow: {
|
|
gap: 6,
|
|
marginBottom: 10,
|
|
},
|
|
timeItem: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
},
|
|
timeLabel: {
|
|
fontSize: 12,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
timeValue: {
|
|
fontSize: 12,
|
|
fontWeight: "500",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
locationContainer: {
|
|
padding: 10,
|
|
borderRadius: 6,
|
|
gap: 6,
|
|
marginBottom: 10,
|
|
},
|
|
locationItem: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
},
|
|
locationLabel: {
|
|
fontSize: 11,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
locationValue: {
|
|
fontSize: 11,
|
|
fontWeight: "500",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
catchContainer: {
|
|
marginBottom: 10,
|
|
},
|
|
catchHeader: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
marginBottom: 6,
|
|
},
|
|
catchLabel: {
|
|
fontSize: 13,
|
|
fontWeight: "600",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
catchList: {
|
|
gap: 4,
|
|
paddingLeft: 22,
|
|
},
|
|
catchItem: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
},
|
|
fishName: {
|
|
fontSize: 12,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
fishAmount: {
|
|
fontSize: 12,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
moreText: {
|
|
fontSize: 12,
|
|
fontWeight: "500",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
weatherRow: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
},
|
|
weatherText: {
|
|
fontSize: 12,
|
|
fontStyle: "italic",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
});
|