diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index e08aa65..9444e80 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -3,8 +3,10 @@ import { Tabs, useSegments } from "expo-router"; import { HapticTab } from "@/components/haptic-tab"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { Colors } from "@/constants/theme"; +import { queryProfile } from "@/controller/AuthController"; import { useI18n } from "@/hooks/use-i18n"; import { useColorScheme } from "@/hooks/use-theme-context"; +import { addUserStorage } from "@/utils/storage"; import { useEffect, useRef } from "react"; export default function TabLayout() { @@ -29,6 +31,23 @@ export default function TabLayout() { } }, [currentSegment]); + useEffect(() => { + const getUserProfile = async () => { + try { + const resp = await queryProfile(); + if (resp.data && resp.status === 200) { + await addUserStorage( + resp.data.id || "", + resp.data.metadata?.user_type || "" + ); + } + } catch (error) { + console.error("Error when get Profile: ", error); + } + }; + getUserProfile(); + }, []); + return ( createStyles(colors, colorScheme), + [colors, colorScheme] + ); + + const [selected, setSelected] = useState<"ships" | "devices" | "fleets">( + "ships" + ); + const [containerWidth, setContainerWidth] = useState(0); + const indicatorTranslate = useRef(new Animated.Value(0)).current; + const SEGMENT_COUNT = 3; + const indexMap: Record = { + ships: 0, + devices: 1, + fleets: 2, + }; + + const SegmentButton = ({ + label, + active, + onPress, + }: { + label: string; + active?: boolean; + onPress?: () => void; + }) => { + return ( + + + {label} + + + ); + }; + + useEffect(() => { + if (containerWidth <= 0) return; + const segmentWidth = containerWidth / SEGMENT_COUNT; + const toValue = indexMap[selected] * segmentWidth; + Animated.spring(indicatorTranslate, { + toValue, + useNativeDriver: true, + friction: 14, + tension: 100, + }).start(); + }, [selected, containerWidth, indicatorTranslate]); return ( - - + + - Quản lý tàu + + setContainerWidth(e.nativeEvent.layout.width)} + > + {/* sliding indicator */} + {containerWidth > 0 && ( + + )} + + setSelected("ships")} + /> + setSelected("devices")} + /> + setSelected("fleets")} + /> + + + + + {selected === "ships" && } + {selected === "devices" && } + {selected === "fleets" && } + - + ); } -const styles = StyleSheet.create({ - scrollContent: { - flexGrow: 1, - }, - container: { - alignItems: "center", - padding: 15, - }, - titleText: { - fontSize: 32, - fontWeight: "700", - lineHeight: 40, - marginBottom: 30, - fontFamily: Platform.select({ - ios: "System", - android: "Roboto", - default: "System", - }), - }, -}); +const createStyles = (colors: typeof Colors.light, scheme: ColorScheme) => + StyleSheet.create({ + scrollContent: { + flexGrow: 1, + }, + container: { + alignItems: "center", + flex: 1, + }, + + header: { + width: "100%", + paddingVertical: 8, + paddingHorizontal: 4, + }, + segmentContainer: { + flexDirection: "row", + backgroundColor: colors.backgroundSecondary, + borderRadius: 10, + padding: 4, + alignSelf: "stretch", + }, + segmentButton: { + flex: 1, + paddingVertical: 8, + alignItems: "center", + justifyContent: "center", + borderRadius: 8, + }, + segmentButtonActive: { + backgroundColor: scheme === "dark" ? "#435B66" : colors.surface, + shadowColor: scheme === "dark" ? "transparent" : "#000", + shadowOpacity: scheme === "dark" ? 0 : 0.1, + shadowRadius: 4, + elevation: scheme === "dark" ? 0 : 2, + }, + segmentText: { + fontSize: 14, + fontWeight: "600", + color: colors.textSecondary, + }, + segmentTextActive: { + color: colors.text, + }, + indicator: { + position: "absolute", + left: 4, + top: 4, + bottom: 4, + backgroundColor: scheme === "dark" ? "#435B66" : colors.surface, + borderRadius: 8, + shadowColor: scheme === "dark" ? "transparent" : "#000", + shadowOpacity: scheme === "dark" ? 0 : 0.1, + shadowRadius: 4, + elevation: scheme === "dark" ? 0 : 2, + }, + contentWrapper: { + flex: 1, + alignSelf: "stretch", + width: "100%", + }, + }); diff --git a/app/(tabs)/tripInfo.tsx b/app/(tabs)/tripInfo.tsx deleted file mode 100644 index 6534c2b..0000000 --- a/app/(tabs)/tripInfo.tsx +++ /dev/null @@ -1,83 +0,0 @@ -// import ButtonCancelTrip from "@/components/ButtonCancelTrip"; -// import ButtonCreateNewHaulOrTrip from "@/components/ButtonCreateNewHaulOrTrip"; -// import ButtonEndTrip from "@/components/ButtonEndTrip"; -// import CrewListTable from "@/components/tripInfo/CrewListTable"; -// import FishingToolsTable from "@/components/tripInfo/FishingToolsList"; -// import NetListTable from "@/components/tripInfo/NetListTable"; -// import TripCostTable from "@/components/tripInfo/TripCostTable"; -// import { useI18n } from "@/hooks/use-i18n"; -// import { useThemeContext } from "@/hooks/use-theme-context"; -// import { Platform, ScrollView, StyleSheet, Text, View } from "react-native"; -// import { SafeAreaView } from "react-native-safe-area-context"; - -// export default function TripInfoScreen() { -// const { t } = useI18n(); -// const { colors } = useThemeContext(); -// return ( -// -// -// -// {t("trip.infoTrip")} -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// ); -// } - -// const styles = StyleSheet.create({ -// safeArea: { -// flex: 1, -// paddingBottom: 5, -// }, -// scrollContent: { -// flexGrow: 1, -// }, -// header: { -// width: "100%", -// paddingHorizontal: 15, -// paddingTop: 15, -// paddingBottom: 10, -// alignItems: "center", -// }, -// buttonWrapper: { -// width: "100%", -// flexDirection: "row", -// justifyContent: "flex-end", -// }, -// container: { -// alignItems: "center", -// paddingHorizontal: 15, -// }, -// buttonRow: { -// flexDirection: "row", -// gap: 10, -// marginTop: 15, -// marginBottom: 15, -// }, -// titleText: { -// fontSize: 32, -// fontWeight: "700", -// lineHeight: 40, -// paddingBottom: 10, -// fontFamily: Platform.select({ -// ios: "System", -// android: "Roboto", -// default: "System", -// }), -// }, -// }); diff --git a/components/ShipSearchForm.tsx b/components/ShipSearchForm.tsx index edff56f..ce404d6 100644 --- a/components/ShipSearchForm.tsx +++ b/components/ShipSearchForm.tsx @@ -1,6 +1,6 @@ import { Colors } from "@/config"; -import { queryShipGroups } from "@/controller/DeviceController"; import { ColorScheme, useTheme } from "@/hooks/use-theme-context"; +import { useShipGroups } from "@/state/use-ship-groups"; import { useShipTypes } from "@/state/use-ship-types"; import { useEffect, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; @@ -44,8 +44,8 @@ const ShipSearchForm = (props: ShipSearchFormProps) => { [colors, colorScheme] ); const { shipTypes, getShipTypes } = useShipTypes(); - const [groupShips, setGroupShips] = useState([]); const [slideAnim] = useState(new Animated.Value(0)); + const { shipGroups, getShipGroups } = useShipGroups(); const { control, handleSubmit, reset, watch } = useForm({ defaultValues: { @@ -70,8 +70,10 @@ const ShipSearchForm = (props: ShipSearchFormProps) => { }, [shipTypes]); useEffect(() => { - getShipGroups(); - }, []); + if (shipGroups === null) { + getShipGroups(); + } + }, [props.isOpen]); useEffect(() => { if (props.isOpen) { @@ -107,17 +109,6 @@ const ShipSearchForm = (props: ShipSearchFormProps) => { } }, [props.initialValues]); - const getShipGroups = async () => { - try { - const response = await queryShipGroups(); - if (response && response.data) { - setGroupShips(response.data); - } - } catch (error) { - console.error("Error fetching ship groups:", error); - } - }; - const alarmListLabel = [ { label: "Tiếp cận vùng hạn chế", @@ -366,10 +357,12 @@ const ShipSearchForm = (props: ShipSearchFormProps) => { name="ship_group_id" render={({ field: { onChange, value } }) => ( + )} + /> + {errors.ship_type && ( + + {errors.ship_type.message} + + )} + + + {/* Home Port */} + + + Cảng đăng ký đỗ * + + ( + + )} + /> + + )} + + {/* Device/Thing - Only show in create mode */} + {props.type === "create" && ( + + + Thiết bị kết nối * + + ( +