thêm giao diện quản lý thuyền
This commit is contained in:
@@ -1,35 +1,178 @@
|
||||
import { Platform, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import DevicesScreen from "@/components/manager/devices";
|
||||
import FleetsScreen from "@/components/manager/fleets";
|
||||
import ShipsScreen from "@/components/manager/ships";
|
||||
import { ThemedText } from "@/components/themed-text";
|
||||
import { ThemedView } from "@/components/themed-view";
|
||||
import { Colors } from "@/config";
|
||||
import { ColorScheme, useTheme } from "@/hooks/use-theme-context";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Animated, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
export default function manager() {
|
||||
const { colors, colorScheme } = useTheme();
|
||||
const styles = useMemo(
|
||||
() => 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<string, number> = {
|
||||
ships: 0,
|
||||
devices: 1,
|
||||
fleets: 2,
|
||||
};
|
||||
|
||||
const SegmentButton = ({
|
||||
label,
|
||||
active,
|
||||
onPress,
|
||||
}: {
|
||||
label: string;
|
||||
active?: boolean;
|
||||
onPress?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.segmentButton, active && styles.segmentButtonActive]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<ThemedText
|
||||
style={[styles.segmentText, active && styles.segmentTextActive]}
|
||||
>
|
||||
{label}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={["top"]}>
|
||||
<ThemedView style={styles.scrollContent}>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.titleText}>Quản lý tàu </Text>
|
||||
<ThemedView style={styles.header}>
|
||||
<View
|
||||
style={styles.segmentContainer}
|
||||
onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
|
||||
>
|
||||
{/* sliding indicator */}
|
||||
{containerWidth > 0 && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.indicator,
|
||||
{
|
||||
width: Math.max(containerWidth / SEGMENT_COUNT - 8, 0),
|
||||
transform: [{ translateX: indicatorTranslate }],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SegmentButton
|
||||
label="Tàu"
|
||||
active={selected === "ships"}
|
||||
onPress={() => setSelected("ships")}
|
||||
/>
|
||||
<SegmentButton
|
||||
label="Thiết bị"
|
||||
active={selected === "devices"}
|
||||
onPress={() => setSelected("devices")}
|
||||
/>
|
||||
<SegmentButton
|
||||
label="Đội tàu"
|
||||
active={selected === "fleets"}
|
||||
onPress={() => setSelected("fleets")}
|
||||
/>
|
||||
</View>
|
||||
</ThemedView>
|
||||
|
||||
<View style={styles.contentWrapper}>
|
||||
{selected === "ships" && <ShipsScreen />}
|
||||
{selected === "devices" && <DevicesScreen />}
|
||||
{selected === "fleets" && <FleetsScreen />}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
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%",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user