update tab nhật ký (Lọc, Ngôn ngữ,...)

This commit is contained in:
2025-12-07 20:23:10 +07:00
parent 0672f8adf9
commit e405a0bcfa
17 changed files with 851 additions and 332 deletions

View File

@@ -0,0 +1,319 @@
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Modal,
Platform,
ScrollView,
TextInput,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useThings } from "@/state/use-thing";
import { useI18n } from "@/hooks/use-i18n";
interface ShipOption {
id: string;
shipName: string;
}
interface ShipDropdownProps {
value: ShipOption | null;
onChange: (value: ShipOption | null) => void;
}
export default function ShipDropdown({ value, onChange }: ShipDropdownProps) {
const { t } = useI18n();
const [isOpen, setIsOpen] = useState(false);
const [searchText, setSearchText] = useState("");
const { things } = useThings();
// Convert things to ship options, filter out items without id
const shipOptions: ShipOption[] =
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 handleSelect = (ship: ShipOption | null) => {
onChange(ship);
setIsOpen(false);
setSearchText("");
};
const displayValue = value ? value.shipName : t("diary.shipDropdown.placeholder");
return (
<View style={styles.container}>
<Text style={styles.label}>{t("diary.shipDropdown.label")}</Text>
<TouchableOpacity
style={styles.selector}
onPress={() => setIsOpen(true)}
activeOpacity={0.7}
>
<Text style={[styles.selectorText, !value && styles.placeholder]}>
{displayValue}
</Text>
<Ionicons name="chevron-down" size={20} color="#6B7280" />
</TouchableOpacity>
<Modal
visible={isOpen}
transparent
animationType="fade"
onRequestClose={() => setIsOpen(false)}
>
<TouchableOpacity
style={styles.modalOverlay}
activeOpacity={1}
onPress={() => setIsOpen(false)}
>
<View
style={styles.modalContent}
onStartShouldSetResponder={() => true}
>
{/* Search Input */}
<View style={styles.searchContainer}>
<Ionicons
name="search"
size={20}
color="#9CA3AF"
style={styles.searchIcon}
/>
<TextInput
style={styles.searchInput}
placeholder={t("diary.shipDropdown.searchPlaceholder")}
placeholderTextColor="#9CA3AF"
value={searchText}
onChangeText={setSearchText}
autoCapitalize="none"
/>
{searchText.length > 0 && (
<TouchableOpacity onPress={() => setSearchText("")}>
<Ionicons name="close-circle" size={20} color="#9CA3AF" />
</TouchableOpacity>
)}
</View>
<ScrollView style={styles.optionsList}>
{/* Option to clear selection */}
<TouchableOpacity
style={[styles.option, !value && styles.selectedOption]}
onPress={() => handleSelect(null)}
>
<Text
style={[
styles.optionText,
styles.placeholderOption,
!value && styles.selectedOptionText,
]}
>
{t("diary.shipDropdown.allShips")}
</Text>
{!value && (
<Ionicons name="checkmark" size={20} color="#3B82F6" />
)}
</TouchableOpacity>
{filteredShips.map((ship) => (
<TouchableOpacity
key={ship.id}
style={[
styles.option,
value?.id === ship.id && styles.selectedOption,
]}
onPress={() => handleSelect(ship)}
>
<View style={styles.shipInfo}>
<Text
style={[
styles.shipName,
value?.id === ship.id && styles.selectedOptionText,
]}
>
{ship.shipName}
</Text>
</View>
{value?.id === ship.id && (
<Ionicons name="checkmark" size={20} color="#3B82F6" />
)}
</TouchableOpacity>
))}
{filteredShips.length === 0 && searchText.length > 0 && (
<View style={styles.emptyState}>
<Text style={styles.emptyText}>
{t("diary.shipDropdown.noShipsFound")}
</Text>
</View>
)}
</ScrollView>
</View>
</TouchableOpacity>
</Modal>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginBottom: 20,
},
label: {
fontSize: 16,
fontWeight: "600",
color: "#111827",
marginBottom: 8,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
selector: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: "#FFFFFF",
borderWidth: 1,
borderColor: "#D1D5DB",
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 12,
},
selectorText: {
fontSize: 16,
color: "#111827",
flex: 1,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
placeholder: {
color: "#9CA3AF",
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "center",
alignItems: "center",
},
modalContent: {
backgroundColor: "#FFFFFF",
borderRadius: 12,
width: "85%",
maxHeight: "70%",
overflow: "hidden",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
searchContainer: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: "#F3F4F6",
backgroundColor: "#F9FAFB",
},
searchIcon: {
marginRight: 8,
},
searchInput: {
flex: 1,
fontSize: 16,
color: "#111827",
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,
borderBottomColor: "#F3F4F6",
},
selectedOption: {
backgroundColor: "#EFF6FF",
},
optionText: {
fontSize: 16,
color: "#111827",
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
placeholderOption: {
fontStyle: "italic",
color: "#6B7280",
},
selectedOptionText: {
color: "#3B82F6",
fontWeight: "600",
},
shipInfo: {
flex: 1,
},
shipName: {
fontSize: 16,
color: "#111827",
fontWeight: "500",
marginBottom: 2,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
regNumber: {
fontSize: 14,
color: "#6B7280",
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
emptyState: {
paddingVertical: 24,
alignItems: "center",
},
emptyText: {
fontSize: 14,
color: "#9CA3AF",
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
});