Files
sgw-owner-app/components/diary/addTripModal/AutoFillSection.tsx

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",
}),
},
});