hiển thị thuyền thông tin tàu
This commit is contained in:
522
components/ShipSearchForm.tsx
Normal file
522
components/ShipSearchForm.tsx
Normal file
@@ -0,0 +1,522 @@
|
||||
import { Colors } from "@/config";
|
||||
import { queryShipGroups } from "@/controller/DeviceController";
|
||||
import { ColorScheme, useTheme } from "@/hooks/use-theme-context";
|
||||
import { useShipTypes } from "@/state/use-ship-types";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
Animated,
|
||||
Modal,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import Select from "./Select";
|
||||
import Slider from "./Slider";
|
||||
|
||||
interface ShipSearchFormProps {
|
||||
initialValues?: Partial<SearchShipResponse>;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit?: (data: SearchShipResponse) => void;
|
||||
}
|
||||
|
||||
export interface SearchShipResponse {
|
||||
ship_name: string;
|
||||
ship_length: [number, number];
|
||||
reg_number: string;
|
||||
ship_power: [number, number];
|
||||
ship_type: string | number;
|
||||
alarm_list: string;
|
||||
ship_group_id: string;
|
||||
group_id: string;
|
||||
}
|
||||
|
||||
const ShipSearchForm = (props: ShipSearchFormProps) => {
|
||||
const { colors, colorScheme } = useTheme();
|
||||
const styles = useMemo(
|
||||
() => createStyles(colors, colorScheme),
|
||||
[colors, colorScheme]
|
||||
);
|
||||
const { shipTypes, getShipTypes } = useShipTypes();
|
||||
const [groupShips, setGroupShips] = useState<Model.ShipGroup[]>([]);
|
||||
const [slideAnim] = useState(new Animated.Value(0));
|
||||
|
||||
const { control, handleSubmit, reset, watch } = useForm<SearchShipResponse>({
|
||||
defaultValues: {
|
||||
ship_name: props.initialValues?.ship_name || "",
|
||||
reg_number: props.initialValues?.reg_number || "",
|
||||
ship_length: [0, 100],
|
||||
ship_power: [0, 100000],
|
||||
ship_type: props.initialValues?.ship_type || "",
|
||||
alarm_list: props.initialValues?.alarm_list || "",
|
||||
ship_group_id: props.initialValues?.ship_group_id || "",
|
||||
group_id: props.initialValues?.group_id || "",
|
||||
},
|
||||
});
|
||||
|
||||
const shipLengthValue = watch("ship_length");
|
||||
const shipPowerValue = watch("ship_power");
|
||||
|
||||
useEffect(() => {
|
||||
if (shipTypes.length === 0) {
|
||||
getShipTypes();
|
||||
}
|
||||
}, [shipTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
getShipGroups();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isOpen) {
|
||||
Animated.spring(slideAnim, {
|
||||
toValue: 1,
|
||||
useNativeDriver: true,
|
||||
tension: 50,
|
||||
friction: 8,
|
||||
}).start();
|
||||
} else {
|
||||
slideAnim.setValue(0);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.initialValues) {
|
||||
reset({
|
||||
ship_name: props.initialValues.ship_name || "",
|
||||
reg_number: props.initialValues.reg_number || "",
|
||||
ship_length: [0, 100],
|
||||
ship_power: [0, 100000],
|
||||
ship_type: props.initialValues.ship_type || "",
|
||||
alarm_list: props.initialValues.alarm_list || "",
|
||||
ship_group_id: props.initialValues.ship_group_id || "",
|
||||
group_id: props.initialValues.group_id || "",
|
||||
});
|
||||
}
|
||||
}, [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ế",
|
||||
value: "50:10",
|
||||
},
|
||||
{
|
||||
label: "Đã ra (vào) vùng hạn chế)",
|
||||
value: "50:11",
|
||||
},
|
||||
{
|
||||
label: "Đang đánh bắt trong vùng hạn chế",
|
||||
value: "50:12",
|
||||
},
|
||||
];
|
||||
|
||||
const onSubmitForm = (data: SearchShipResponse) => {
|
||||
props.onSubmit?.(data);
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset({
|
||||
ship_name: "",
|
||||
reg_number: "",
|
||||
ship_length: [0, 100],
|
||||
ship_power: [0, 100000],
|
||||
ship_type: "",
|
||||
alarm_list: "",
|
||||
ship_group_id: "",
|
||||
group_id: "",
|
||||
});
|
||||
};
|
||||
|
||||
const translateY = slideAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [600, 0],
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="fade"
|
||||
transparent={true}
|
||||
visible={props.isOpen}
|
||||
onRequestClose={props.onClose}
|
||||
>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<Pressable style={styles.backdrop} onPress={props.onClose}>
|
||||
<Animated.View
|
||||
style={[styles.modalContent, { transform: [{ translateY }] }]}
|
||||
onStartShouldSetResponder={() => true}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.dragIndicator} />
|
||||
<Text style={[styles.headerTitle, { color: colors.text }]}>
|
||||
Tìm kiếm tàu
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={props.onClose}
|
||||
style={styles.closeButton}
|
||||
>
|
||||
<Text style={[styles.closeButtonText, { color: colors.text }]}>
|
||||
✕
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Form Content */}
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.formSection}>
|
||||
{/* Tên tàu */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Tên tàu
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="ship_name"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<TextInput
|
||||
placeholder="Hoàng Sa 001"
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
style={[
|
||||
styles.input,
|
||||
{
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.surface,
|
||||
color: colors.text,
|
||||
},
|
||||
]}
|
||||
value={value}
|
||||
onChangeText={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Số đăng ký */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Số đăng ký
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="reg_number"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<TextInput
|
||||
placeholder="VN-00001"
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
style={[
|
||||
styles.input,
|
||||
{
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.surface,
|
||||
color: colors.text,
|
||||
},
|
||||
]}
|
||||
value={value}
|
||||
onChangeText={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Chiều dài */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Chiều dài ({shipLengthValue[0]}m - {shipLengthValue[1]}m)
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="ship_length"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<View style={styles.sliderContainer}>
|
||||
<Slider
|
||||
range={true}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={value}
|
||||
marks={{
|
||||
0: "0m",
|
||||
50: "50m",
|
||||
100: "100m",
|
||||
}}
|
||||
onValueChange={(val) =>
|
||||
onChange(val as [number, number])
|
||||
}
|
||||
activeColor={colors.primary}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Công suất */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Công suất ({shipPowerValue[0]}kW - {shipPowerValue[1]}kW)
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="ship_power"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<View style={styles.sliderContainer}>
|
||||
<Slider
|
||||
range={true}
|
||||
min={0}
|
||||
max={100000}
|
||||
step={1000}
|
||||
value={value}
|
||||
marks={{
|
||||
0: "0kW",
|
||||
50000: "50,000kW",
|
||||
100000: "100,000kW",
|
||||
}}
|
||||
onValueChange={(val) =>
|
||||
onChange(val as [number, number])
|
||||
}
|
||||
activeColor={colors.primary}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Loại tàu */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Loại tàu
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="ship_type"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
options={shipTypes.map((type) => ({
|
||||
label: type.name || "",
|
||||
value: type.id || 0,
|
||||
}))}
|
||||
placeholder="Chọn loại tàu"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Cảnh báo */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Cảnh báo
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="alarm_list"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
options={alarmListLabel.map((type) => ({
|
||||
label: type.label || "",
|
||||
value: type.value || "",
|
||||
}))}
|
||||
placeholder="Chọn loại cảnh báo"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Đội tàu */}
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={[styles.label, { color: colors.text }]}>
|
||||
Đội tàu
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
name="ship_group_id"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
options={groupShips.map((group) => ({
|
||||
label: group.name || "",
|
||||
value: group.id || "",
|
||||
}))}
|
||||
placeholder="Chọn đội tàu"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<View
|
||||
style={[styles.actionButtons, { borderTopColor: colors.border }]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[styles.resetButton, { borderColor: colors.border }]}
|
||||
onPress={onReset}
|
||||
>
|
||||
<Text style={[styles.resetButtonText, { color: colors.text }]}>
|
||||
Đặt lại
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.submitButton,
|
||||
{ backgroundColor: colors.primary },
|
||||
]}
|
||||
onPress={handleSubmit(onSubmitForm)}
|
||||
>
|
||||
<Text style={styles.submitButtonText}>Tìm kiếm</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
</GestureHandlerRootView>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const createStyles = (colors: typeof Colors.light, scheme: ColorScheme) =>
|
||||
StyleSheet.create({
|
||||
backdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
modalContent: {
|
||||
height: "85%",
|
||||
backgroundColor: colors.background,
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: -4,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 8,
|
||||
elevation: 10,
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
position: "relative",
|
||||
},
|
||||
dragIndicator: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
width: 40,
|
||||
height: 4,
|
||||
backgroundColor: colors.border,
|
||||
borderRadius: 2,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "700",
|
||||
textAlign: "center",
|
||||
},
|
||||
closeButton: {
|
||||
position: "absolute",
|
||||
right: 16,
|
||||
top: 16,
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 16,
|
||||
},
|
||||
closeButtonText: {
|
||||
fontSize: 20,
|
||||
fontWeight: "300",
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 20,
|
||||
},
|
||||
formSection: {
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 20,
|
||||
},
|
||||
fieldGroup: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
label: {
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
marginBottom: 10,
|
||||
},
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
fontSize: 15,
|
||||
},
|
||||
sliderContainer: {
|
||||
paddingHorizontal: 4,
|
||||
paddingTop: 8,
|
||||
},
|
||||
actionButtons: {
|
||||
flexDirection: "row",
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
gap: 12,
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
resetButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
resetButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
submitButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
submitButtonText: {
|
||||
color: "#ffffff",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
|
||||
export default ShipSearchForm;
|
||||
Reference in New Issue
Block a user