389 lines
10 KiB
TypeScript
389 lines
10 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Modal,
|
|
Platform,
|
|
ScrollView,
|
|
TextInput,
|
|
ActivityIndicator,
|
|
Alert,
|
|
} from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useThings } from "@/state/use-thing";
|
|
import { useI18n } from "@/hooks/use-i18n";
|
|
import { useThemeContext } from "@/hooks/use-theme-context";
|
|
import { queryLastTrip } from "@/controller/TripController";
|
|
import { showErrorToast } from "@/services/toast_service";
|
|
|
|
interface AutoFillSectionProps {
|
|
onAutoFill: (tripData: Model.Trip, selectedShipId: string) => void;
|
|
}
|
|
|
|
export default function AutoFillSection({ onAutoFill }: AutoFillSectionProps) {
|
|
const { t } = useI18n();
|
|
const { colors } = useThemeContext();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [searchText, setSearchText] = useState("");
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const { things } = useThings();
|
|
|
|
// Convert things to ship options
|
|
const shipOptions =
|
|
things
|
|
?.filter((thing) => thing.id != null)
|
|
.map((thing) => ({
|
|
id: thing.id as string,
|
|
shipName: thing.metadata?.ship_name || "",
|
|
})) || [];
|
|
|
|
// Filter ships based on search text
|
|
const filteredShips = shipOptions.filter((ship) => {
|
|
const searchLower = searchText.toLowerCase();
|
|
return ship.shipName.toLowerCase().includes(searchLower);
|
|
});
|
|
|
|
const handleSelectShip = async (shipId: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const response = await queryLastTrip(shipId);
|
|
if (response.data) {
|
|
// Close the modal first before showing alert
|
|
setIsOpen(false);
|
|
setSearchText("");
|
|
|
|
// Pass shipId (thingId) along with trip data for filling ShipSelector
|
|
onAutoFill(response.data, shipId);
|
|
|
|
// Use Alert instead of Toast so it appears above all modals
|
|
Alert.alert(
|
|
t("common.success"),
|
|
t("diary.autoFill.success")
|
|
);
|
|
} else {
|
|
showErrorToast(t("diary.autoFill.noData"));
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching last trip:", error);
|
|
showErrorToast(t("diary.autoFill.error"));
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const themedStyles = {
|
|
container: {
|
|
backgroundColor: colors.backgroundSecondary,
|
|
borderColor: colors.primary,
|
|
},
|
|
label: { color: colors.text },
|
|
description: { color: colors.textSecondary },
|
|
button: {
|
|
backgroundColor: colors.primary,
|
|
},
|
|
modalContent: { backgroundColor: colors.card },
|
|
searchContainer: {
|
|
backgroundColor: colors.backgroundSecondary,
|
|
borderColor: colors.border,
|
|
},
|
|
searchInput: { color: colors.text },
|
|
option: { borderBottomColor: colors.separator },
|
|
optionText: { color: colors.text },
|
|
emptyText: { color: colors.textSecondary },
|
|
};
|
|
|
|
return (
|
|
<View style={[styles.container, themedStyles.container]}>
|
|
<View style={styles.contentWrapper}>
|
|
<View style={styles.iconContainer}>
|
|
<Ionicons name="flash" size={24} color={colors.primary} />
|
|
</View>
|
|
<View style={styles.textContainer}>
|
|
<Text style={[styles.label, themedStyles.label]}>
|
|
{t("diary.autoFill.title")}
|
|
</Text>
|
|
<Text style={[styles.description, themedStyles.description]}>
|
|
{t("diary.autoFill.description")}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<TouchableOpacity
|
|
style={[styles.button, themedStyles.button]}
|
|
onPress={() => setIsOpen(true)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={styles.buttonText}>{t("diary.autoFill.selectShip")}</Text>
|
|
</TouchableOpacity>
|
|
|
|
<Modal
|
|
visible={isOpen}
|
|
transparent
|
|
animationType="fade"
|
|
onRequestClose={() => setIsOpen(false)}
|
|
>
|
|
<TouchableOpacity
|
|
style={styles.modalOverlay}
|
|
activeOpacity={1}
|
|
onPress={() => setIsOpen(false)}
|
|
>
|
|
<View
|
|
style={[styles.modalContent, themedStyles.modalContent]}
|
|
onStartShouldSetResponder={() => true}
|
|
>
|
|
{/* Header */}
|
|
<View style={styles.modalHeader}>
|
|
<Text style={[styles.modalTitle, { color: colors.text }]}>
|
|
{t("diary.autoFill.modalTitle")}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Search Input */}
|
|
<View
|
|
style={[styles.searchContainer, themedStyles.searchContainer]}
|
|
>
|
|
<Ionicons
|
|
name="search"
|
|
size={20}
|
|
color={colors.textSecondary}
|
|
style={styles.searchIcon}
|
|
/>
|
|
<TextInput
|
|
style={[styles.searchInput, themedStyles.searchInput]}
|
|
placeholder={t("diary.searchShip")}
|
|
placeholderTextColor={colors.textSecondary}
|
|
value={searchText}
|
|
onChangeText={setSearchText}
|
|
autoCapitalize="none"
|
|
/>
|
|
{searchText.length > 0 && (
|
|
<TouchableOpacity onPress={() => setSearchText("")}>
|
|
<Ionicons
|
|
name="close-circle"
|
|
size={20}
|
|
color={colors.textSecondary}
|
|
/>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
{isLoading ? (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
<Text style={[styles.loadingText, { color: colors.textSecondary }]}>
|
|
{t("diary.autoFill.loading")}
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<ScrollView style={styles.optionsList}>
|
|
{/* Filtered ship options */}
|
|
{filteredShips.length > 0 ? (
|
|
filteredShips.map((ship) => (
|
|
<TouchableOpacity
|
|
key={ship.id}
|
|
style={[styles.option, themedStyles.option]}
|
|
onPress={() => handleSelectShip(ship.id)}
|
|
>
|
|
<View style={styles.optionContent}>
|
|
<Ionicons
|
|
name="boat"
|
|
size={20}
|
|
color={colors.primary}
|
|
style={styles.optionIcon}
|
|
/>
|
|
<Text style={[styles.optionText, themedStyles.optionText]}>
|
|
{ship.shipName}
|
|
</Text>
|
|
</View>
|
|
<Ionicons
|
|
name="chevron-forward"
|
|
size={20}
|
|
color={colors.textSecondary}
|
|
/>
|
|
</TouchableOpacity>
|
|
))
|
|
) : (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={[styles.emptyText, themedStyles.emptyText]}>
|
|
{t("diary.noShipsFound")}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
</Modal>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
marginBottom: 20,
|
|
padding: 16,
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
borderStyle: "dashed",
|
|
},
|
|
contentWrapper: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
marginBottom: 12,
|
|
},
|
|
iconContainer: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
backgroundColor: "rgba(59, 130, 246, 0.1)",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginRight: 12,
|
|
},
|
|
textContainer: {
|
|
flex: 1,
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
marginBottom: 4,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
description: {
|
|
fontSize: 13,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
button: {
|
|
paddingVertical: 10,
|
|
paddingHorizontal: 16,
|
|
borderRadius: 8,
|
|
alignItems: "center",
|
|
},
|
|
buttonText: {
|
|
fontSize: 14,
|
|
fontWeight: "600",
|
|
color: "#FFFFFF",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
},
|
|
modalContent: {
|
|
borderRadius: 12,
|
|
width: "85%",
|
|
maxHeight: "70%",
|
|
overflow: "hidden",
|
|
shadowColor: "#000",
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 4,
|
|
},
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 8,
|
|
elevation: 8,
|
|
},
|
|
modalHeader: {
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 16,
|
|
},
|
|
modalTitle: {
|
|
fontSize: 18,
|
|
fontWeight: "700",
|
|
textAlign: "center",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
searchContainer: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
borderBottomWidth: 1,
|
|
},
|
|
searchIcon: {
|
|
marginRight: 8,
|
|
},
|
|
searchInput: {
|
|
flex: 1,
|
|
fontSize: 16,
|
|
padding: 0,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
optionsList: {
|
|
maxHeight: 350,
|
|
},
|
|
option: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 16,
|
|
borderBottomWidth: 1,
|
|
},
|
|
optionContent: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
flex: 1,
|
|
},
|
|
optionIcon: {
|
|
marginRight: 12,
|
|
},
|
|
optionText: {
|
|
fontSize: 16,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
emptyContainer: {
|
|
paddingVertical: 24,
|
|
alignItems: "center",
|
|
},
|
|
emptyText: {
|
|
fontSize: 14,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
loadingContainer: {
|
|
paddingVertical: 40,
|
|
alignItems: "center",
|
|
},
|
|
loadingText: {
|
|
marginTop: 12,
|
|
fontSize: 14,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
});
|