528 lines
16 KiB
TypeScript
528 lines
16 KiB
TypeScript
import { Colors } from "@/config";
|
|
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";
|
|
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 [slideAnim] = useState(new Animated.Value(0));
|
|
const { shipGroups, getShipGroups } = useShipGroups();
|
|
|
|
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(() => {
|
|
if (shipGroups === null) {
|
|
getShipGroups();
|
|
}
|
|
}, [props.isOpen]);
|
|
|
|
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: [
|
|
props.initialValues.ship_length?.[0] || 0,
|
|
props.initialValues.ship_length?.[1] || 100,
|
|
],
|
|
ship_power: [
|
|
props.initialValues.ship_power?.[0] || 0,
|
|
props.initialValues.ship_power?.[1] || 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 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);
|
|
console.log("Data: ", 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"
|
|
mode="multiple"
|
|
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"
|
|
mode="multiple"
|
|
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={
|
|
shipGroups?.map((group) => ({
|
|
label: group.name || "",
|
|
value: group.id || "",
|
|
})) || []
|
|
}
|
|
placeholder="Chọn đội tàu"
|
|
mode="multiple"
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
)}
|
|
/>
|
|
</View>
|
|
<View className="h-12"></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;
|