thêm FormSearch trong map
This commit is contained in:
@@ -1,14 +1,19 @@
|
|||||||
import DraggablePanel from "@/components/DraggablePanel";
|
import DraggablePanel from "@/components/DraggablePanel";
|
||||||
|
import IconButton from "@/components/IconButton";
|
||||||
import type { PolygonWithLabelProps } from "@/components/map/PolygonWithLabel";
|
import type { PolygonWithLabelProps } from "@/components/map/PolygonWithLabel";
|
||||||
import type { PolylineWithLabelProps } from "@/components/map/PolylineWithLabel";
|
import type { PolylineWithLabelProps } from "@/components/map/PolylineWithLabel";
|
||||||
import ShipInfo from "@/components/map/ShipInfo";
|
import ShipInfo from "@/components/map/ShipInfo";
|
||||||
import { TagState, TagStateCallbackPayload } from "@/components/map/TagState";
|
import { TagState, TagStateCallbackPayload } from "@/components/map/TagState";
|
||||||
|
import ShipSearchForm, {
|
||||||
|
SearchShipResponse,
|
||||||
|
} from "@/components/ShipSearchForm";
|
||||||
import { EVENT_SEARCH_THINGS, IOS_PLATFORM, LIGHT_THEME } from "@/constants";
|
import { EVENT_SEARCH_THINGS, IOS_PLATFORM, LIGHT_THEME } from "@/constants";
|
||||||
import { usePlatform } from "@/hooks/use-platform";
|
import { usePlatform } from "@/hooks/use-platform";
|
||||||
import { useThemeContext } from "@/hooks/use-theme-context";
|
import { useThemeContext } from "@/hooks/use-theme-context";
|
||||||
import { searchThingEventBus } from "@/services/device_events";
|
import { searchThingEventBus } from "@/services/device_events";
|
||||||
import { getShipIcon } from "@/services/map_service";
|
import { getShipIcon } from "@/services/map_service";
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
|
import { AntDesign } from "@expo/vector-icons";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
@@ -36,15 +41,34 @@ export default function HomeScreen() {
|
|||||||
const [polygonCoordinates, setPolygonCoordinates] = useState<
|
const [polygonCoordinates, setPolygonCoordinates] = useState<
|
||||||
PolygonWithLabelProps[]
|
PolygonWithLabelProps[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [shipSearchFormOpen, setShipSearchFormOpen] = useState(false);
|
||||||
|
const [isPanelExpanded, setIsPanelExpanded] = useState(false);
|
||||||
const [things, setThings] = useState<Model.ThingsResponse | null>(null);
|
const [things, setThings] = useState<Model.ThingsResponse | null>(null);
|
||||||
const platform = usePlatform();
|
const platform = usePlatform();
|
||||||
const theme = useThemeContext().colorScheme;
|
const theme = useThemeContext().colorScheme;
|
||||||
const scale = useRef(new Animated.Value(0)).current;
|
const scale = useRef(new Animated.Value(0)).current;
|
||||||
const opacity = useRef(new Animated.Value(1)).current;
|
const opacity = useRef(new Animated.Value(1)).current;
|
||||||
|
const [shipSearchFormData, setShipSearchFormData] = useState<
|
||||||
|
SearchShipResponse | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [tagStatePayload, setTagStatePayload] =
|
||||||
|
useState<TagStateCallbackPayload | null>(null);
|
||||||
|
|
||||||
// Thêm state để quản lý tracksViewChanges
|
// Thêm state để quản lý tracksViewChanges
|
||||||
const [tracksViewChanges, setTracksViewChanges] = useState(true);
|
const [tracksViewChanges, setTracksViewChanges] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tagStatePayload) {
|
||||||
|
searchThings();
|
||||||
|
}
|
||||||
|
}, [tagStatePayload]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shipSearchFormData) {
|
||||||
|
searchThings();
|
||||||
|
}
|
||||||
|
}, [shipSearchFormData]);
|
||||||
|
|
||||||
const bodySearchThings: Model.SearchThingBody = {
|
const bodySearchThings: Model.SearchThingBody = {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
@@ -176,6 +200,7 @@ export default function HomeScreen() {
|
|||||||
// }, [banzoneData, entityData]);
|
// }, [banzoneData, entityData]);
|
||||||
|
|
||||||
// Hàm tính radius cố định khi zoom change
|
// Hàm tính radius cố định khi zoom change
|
||||||
|
|
||||||
const calculateRadiusFromZoom = (zoom: number) => {
|
const calculateRadiusFromZoom = (zoom: number) => {
|
||||||
const baseZoom = 10;
|
const baseZoom = 10;
|
||||||
const baseRadius = 100;
|
const baseRadius = 100;
|
||||||
@@ -231,7 +256,8 @@ export default function HomeScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (alarmData?.level === 3) {
|
for (var thing of things?.things || []) {
|
||||||
|
if (thing.metadata?.state_level === 3) {
|
||||||
const loop = Animated.loop(
|
const loop = Animated.loop(
|
||||||
Animated.sequence([
|
Animated.sequence([
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
@@ -263,6 +289,7 @@ export default function HomeScreen() {
|
|||||||
loop.start();
|
loop.start();
|
||||||
return () => loop.stop();
|
return () => loop.stop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [things, scale, opacity]);
|
}, [things, scale, opacity]);
|
||||||
|
|
||||||
// Tắt tracksViewChanges sau khi map đã load xong
|
// Tắt tracksViewChanges sau khi map đã load xong
|
||||||
@@ -276,26 +303,78 @@ export default function HomeScreen() {
|
|||||||
}
|
}
|
||||||
}, [isFirstLoad]);
|
}, [isFirstLoad]);
|
||||||
|
|
||||||
const handleOnPressState = (state: TagStateCallbackPayload) => {
|
const searchThings = async () => {
|
||||||
|
console.log("FormSearch Playload in Search Thing: ", shipSearchFormData);
|
||||||
|
|
||||||
// Xây dựng query state dựa trên logic bạn cung cấp
|
// Xây dựng query state dựa trên logic bạn cung cấp
|
||||||
const stateNormalQuery = state.isNormal ? "normal" : "";
|
const stateNormalQuery = tagStatePayload?.isNormal ? "normal" : "";
|
||||||
const stateSosQuery = state.isSos ? "sos" : "";
|
const stateSosQuery = tagStatePayload?.isSos ? "sos" : "";
|
||||||
const stateWarningQuery = state.isWarning
|
const stateWarningQuery = tagStatePayload?.isWarning
|
||||||
? stateNormalQuery + ",warning"
|
? stateNormalQuery + ",warning"
|
||||||
: stateNormalQuery;
|
: stateNormalQuery;
|
||||||
const stateCriticalQuery = state.isDangerous
|
const stateCriticalQuery = tagStatePayload?.isDangerous
|
||||||
? stateWarningQuery + ",critical"
|
? stateWarningQuery + ",critical"
|
||||||
: stateWarningQuery;
|
: stateWarningQuery;
|
||||||
|
|
||||||
// Nếu bật tất cả filter thì không cần truyền stateQuery
|
// Nếu bật tất cả filter thì không cần truyền stateQuery
|
||||||
const stateQuery =
|
const stateQuery =
|
||||||
state.isNormal && state.isWarning && state.isDangerous && state.isSos
|
tagStatePayload?.isNormal &&
|
||||||
|
tagStatePayload?.isWarning &&
|
||||||
|
tagStatePayload?.isDangerous &&
|
||||||
|
tagStatePayload?.isSos
|
||||||
? ""
|
? ""
|
||||||
: [stateCriticalQuery, stateSosQuery].filter(Boolean).join(",");
|
: [stateCriticalQuery, stateSosQuery].filter(Boolean).join(",");
|
||||||
let metaFormQuery = {};
|
let metaFormQuery = {};
|
||||||
if (state.isDisconected)
|
if (tagStatePayload?.isDisconected)
|
||||||
metaFormQuery = { ...metaFormQuery, connected: false };
|
metaFormQuery = { ...metaFormQuery, connected: false };
|
||||||
|
if (shipSearchFormData?.ship_name) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
ship_name: shipSearchFormData?.ship_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (shipSearchFormData?.reg_number) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
reg_number: shipSearchFormData?.reg_number,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
shipSearchFormData?.ship_length[0] !== 0 ||
|
||||||
|
shipSearchFormData?.ship_length[1] !== 100
|
||||||
|
) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
ship_length: shipSearchFormData?.ship_length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
shipSearchFormData?.ship_power[0] !== 0 ||
|
||||||
|
shipSearchFormData?.ship_power[1] !== 100000
|
||||||
|
) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
ship_power: shipSearchFormData?.ship_power,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (shipSearchFormData?.alarm_list) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
alarm_list: shipSearchFormData?.alarm_list,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (shipSearchFormData?.ship_type) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
ship_type: shipSearchFormData?.ship_type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (shipSearchFormData?.ship_group_id) {
|
||||||
|
metaFormQuery = {
|
||||||
|
...metaFormQuery,
|
||||||
|
ship_group_id: shipSearchFormData?.ship_group_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
// Tạo metadata query
|
// Tạo metadata query
|
||||||
const metaStateQuery =
|
const metaStateQuery =
|
||||||
stateQuery !== "" ? { state_level: stateQuery } : null;
|
stateQuery !== "" ? { state_level: stateQuery } : null;
|
||||||
@@ -312,10 +391,28 @@ export default function HomeScreen() {
|
|||||||
not_empty: "ship_id",
|
not_empty: "ship_id",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
console.log("Search Params: ", searchParams);
|
||||||
|
|
||||||
// Gọi API tìm kiếm
|
// Gọi API tìm kiếm
|
||||||
searchThingEventBus(searchParams);
|
searchThingEventBus(searchParams);
|
||||||
};
|
};
|
||||||
|
const handleOnSubmitSearchForm = async (data: SearchShipResponse) => {
|
||||||
|
setShipSearchFormData(data);
|
||||||
|
setShipSearchFormOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasActiveFilters = shipSearchFormData
|
||||||
|
? shipSearchFormData.ship_name !== "" ||
|
||||||
|
shipSearchFormData.reg_number !== "" ||
|
||||||
|
shipSearchFormData.ship_length[0] !== 0 ||
|
||||||
|
shipSearchFormData.ship_length[1] !== 100 ||
|
||||||
|
shipSearchFormData.ship_power[0] !== 0 ||
|
||||||
|
shipSearchFormData.ship_power[1] !== 100000 ||
|
||||||
|
shipSearchFormData.ship_type !== "" ||
|
||||||
|
shipSearchFormData.alarm_list !== "" ||
|
||||||
|
shipSearchFormData.ship_group_id !== "" ||
|
||||||
|
shipSearchFormData.group_id !== ""
|
||||||
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRootView style={styles.container}>
|
<GestureHandlerRootView style={styles.container}>
|
||||||
@@ -411,7 +508,20 @@ export default function HomeScreen() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MapView>
|
</MapView>
|
||||||
|
<View className="absolute top-20 left-5">
|
||||||
|
{!isPanelExpanded && (
|
||||||
|
<IconButton
|
||||||
|
icon={<AntDesign name="filter" size={16} />}
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
style={{
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: hasActiveFilters ? "#B8D576" : "#fff",
|
||||||
|
}}
|
||||||
|
onPress={() => setShipSearchFormOpen(true)}
|
||||||
|
></IconButton>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
{/* <View className="absolute top-14 right-2 shadow-md">
|
{/* <View className="absolute top-14 right-2 shadow-md">
|
||||||
<SosButton />
|
<SosButton />
|
||||||
</View>
|
</View>
|
||||||
@@ -420,10 +530,11 @@ export default function HomeScreen() {
|
|||||||
{/* Draggable Panel */}
|
{/* Draggable Panel */}
|
||||||
<DraggablePanel
|
<DraggablePanel
|
||||||
minHeightPct={0.1}
|
minHeightPct={0.1}
|
||||||
maxHeightPct={0.5}
|
maxHeightPct={0.6}
|
||||||
initialState="min"
|
initialState="min"
|
||||||
onExpandedChange={(expanded) => {
|
onExpandedChange={(expanded) => {
|
||||||
console.log("Panel expanded:", expanded);
|
console.log("Panel expanded:", expanded);
|
||||||
|
setIsPanelExpanded(expanded);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@@ -437,22 +548,44 @@ export default function HomeScreen() {
|
|||||||
(things?.metadata?.total_thing ?? 0) -
|
(things?.metadata?.total_thing ?? 0) -
|
||||||
(things?.metadata?.total_connected ?? 0) || 0
|
(things?.metadata?.total_connected ?? 0) || 0
|
||||||
}
|
}
|
||||||
onTagPress={handleOnPressState}
|
onTagPress={(tagState) => {
|
||||||
|
setTagStatePayload(tagState);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ width: "100%", paddingVertical: 8, flex: 1 }}>
|
<View style={{ width: "100%", paddingVertical: 8, flex: 1 }}>
|
||||||
|
<View className="flex flex-row items-center">
|
||||||
|
<View className="flex-1 justify-center">
|
||||||
<Text
|
<Text
|
||||||
style={{ fontSize: 20, textAlign: "center", fontWeight: "600" }}
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
textAlign: "center",
|
||||||
|
fontWeight: "600",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Danh sách tàu thuyền
|
Danh sách tàu thuyền
|
||||||
</Text>
|
</Text>
|
||||||
|
</View>
|
||||||
|
{isPanelExpanded && (
|
||||||
|
<IconButton
|
||||||
|
icon={<AntDesign name="filter" size={16} />}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
size="middle"
|
||||||
|
style={{
|
||||||
|
// borderRadius: 10,
|
||||||
|
backgroundColor: hasActiveFilters ? "#B8D576" : "#fff",
|
||||||
|
}}
|
||||||
|
onPress={() => setShipSearchFormOpen(true)}
|
||||||
|
></IconButton>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ width: "100%", marginTop: 8, flex: 1 }}
|
style={{ width: "100%", marginTop: 8, flex: 1 }}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingBottom: 24,
|
paddingBottom: 24,
|
||||||
// backgroundColor: "green",
|
|
||||||
}}
|
}}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
@@ -485,6 +618,12 @@ export default function HomeScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
</DraggablePanel>
|
</DraggablePanel>
|
||||||
|
<ShipSearchForm
|
||||||
|
initialValues={shipSearchFormData}
|
||||||
|
isOpen={shipSearchFormOpen}
|
||||||
|
onClose={() => setShipSearchFormOpen(false)}
|
||||||
|
onSubmit={handleOnSubmitSearchForm}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</GestureHandlerRootView>
|
</GestureHandlerRootView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Pressable,
|
Pressable,
|
||||||
@@ -34,14 +35,10 @@ export default function DraggablePanel({
|
|||||||
children,
|
children,
|
||||||
}: DraggablePanelProps) {
|
}: DraggablePanelProps) {
|
||||||
const { height: screenHeight } = useWindowDimensions();
|
const { height: screenHeight } = useWindowDimensions();
|
||||||
|
const bottomOffset = useBottomTabBarHeight();
|
||||||
// Thêm chiều cao của bottom tab bar vào tính toán
|
|
||||||
const bottomOffset = 80; // 50 là chiều cao mặc định của tab bar
|
|
||||||
|
|
||||||
const minHeight = screenHeight * minHeightPct;
|
const minHeight = screenHeight * minHeightPct;
|
||||||
const maxHeight = screenHeight * maxHeightPct;
|
const maxHeight = screenHeight * maxHeightPct;
|
||||||
|
|
||||||
// State để quản lý icon
|
|
||||||
const [iconName, setIconName] = useState<"chevron-down" | "chevron-up">(
|
const [iconName, setIconName] = useState<"chevron-down" | "chevron-up">(
|
||||||
initialState === "max" ? "chevron-down" : "chevron-up"
|
initialState === "max" ? "chevron-down" : "chevron-up"
|
||||||
);
|
);
|
||||||
@@ -115,17 +112,13 @@ export default function DraggablePanel({
|
|||||||
? screenHeight - maxHeight - bottomOffset + 40
|
? screenHeight - maxHeight - bottomOffset + 40
|
||||||
: screenHeight - minHeight - bottomOffset;
|
: screenHeight - minHeight - bottomOffset;
|
||||||
|
|
||||||
translateY.value = withSpring(
|
|
||||||
targetY,
|
|
||||||
{
|
|
||||||
damping: 20,
|
|
||||||
stiffness: 200,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
"worklet";
|
|
||||||
isExpanded.value = snapToMax;
|
isExpanded.value = snapToMax;
|
||||||
}
|
runOnJS(notifyExpandedChange)(snapToMax);
|
||||||
);
|
|
||||||
|
translateY.value = withSpring(targetY, {
|
||||||
|
damping: 20,
|
||||||
|
stiffness: 50,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => {
|
||||||
@@ -138,7 +131,7 @@ export default function DraggablePanel({
|
|||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => {
|
() => {
|
||||||
const currentHeight = screenHeight - translateY.value - bottomOffset;
|
const currentHeight = screenHeight - translateY.value - bottomOffset;
|
||||||
return currentHeight > minHeight + 10;
|
return currentHeight > minHeight;
|
||||||
},
|
},
|
||||||
(isCurrentlyExpanded) => {
|
(isCurrentlyExpanded) => {
|
||||||
const newIcon = isCurrentlyExpanded ? "chevron-down" : "chevron-up";
|
const newIcon = isCurrentlyExpanded ? "chevron-down" : "chevron-up";
|
||||||
@@ -210,7 +203,7 @@ const styles = StyleSheet.create({
|
|||||||
elevation: 10,
|
elevation: 10,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
paddingTop: 12,
|
paddingTop: 8,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ export interface SelectOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectProps {
|
export interface SelectProps {
|
||||||
value?: string | number;
|
value?: string | number | (string | number)[];
|
||||||
defaultValue?: string | number;
|
defaultValue?: string | number | (string | number)[];
|
||||||
options: SelectOption[];
|
options: SelectOption[];
|
||||||
onChange?: (value: string | number | undefined) => void;
|
onChange?: (value: string | number | (string | number)[] | undefined) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
allowClear?: boolean;
|
allowClear?: boolean;
|
||||||
showSearch?: boolean;
|
showSearch?: boolean;
|
||||||
mode?: "single" | "multiple"; // multiple not implemented yet
|
mode?: "single" | "multiple";
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
size?: "small" | "middle" | "large";
|
size?: "small" | "middle" | "large";
|
||||||
listStyle?: StyleProp<ViewStyle>;
|
listStyle?: StyleProp<ViewStyle>;
|
||||||
@@ -38,7 +38,7 @@ export interface SelectProps {
|
|||||||
/**
|
/**
|
||||||
* Select
|
* Select
|
||||||
* A Select component inspired by Ant Design, adapted for React Native.
|
* A Select component inspired by Ant Design, adapted for React Native.
|
||||||
* Supports single selection, search, clear, loading, disabled states.
|
* Supports single and multiple selection, search, clear, loading, disabled states.
|
||||||
*/
|
*/
|
||||||
const Select: React.FC<SelectProps> = ({
|
const Select: React.FC<SelectProps> = ({
|
||||||
value,
|
value,
|
||||||
@@ -55,16 +55,23 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
listStyle,
|
listStyle,
|
||||||
size = "middle",
|
size = "middle",
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedValue, setSelectedValue] = useState<
|
const initialValue = value ?? defaultValue;
|
||||||
string | number | undefined
|
const [selectedValues, setSelectedValues] = useState<(string | number)[]>(
|
||||||
>(value ?? defaultValue);
|
Array.isArray(initialValue)
|
||||||
|
? initialValue
|
||||||
|
: initialValue
|
||||||
|
? [initialValue]
|
||||||
|
: []
|
||||||
|
);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [containerHeight, setContainerHeight] = useState(0);
|
const [containerHeight, setContainerHeight] = useState(0);
|
||||||
|
const [textHeight, setTextHeight] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedValue(value);
|
const newVal = value ?? defaultValue;
|
||||||
}, [value]);
|
setSelectedValues(Array.isArray(newVal) ? newVal : newVal ? [newVal] : []);
|
||||||
|
}, [value, defaultValue]);
|
||||||
|
|
||||||
const filteredOptions = showSearch
|
const filteredOptions = showSearch
|
||||||
? options.filter((opt) =>
|
? options.filter((opt) =>
|
||||||
@@ -72,17 +79,31 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
)
|
)
|
||||||
: options;
|
: options;
|
||||||
|
|
||||||
const selectedOption = options.find((opt) => opt.value === selectedValue);
|
|
||||||
|
|
||||||
const handleSelect = (val: string | number) => {
|
const handleSelect = (val: string | number) => {
|
||||||
setSelectedValue(val);
|
let newSelected: (string | number)[];
|
||||||
onChange?.(val);
|
if (mode === "single") {
|
||||||
|
newSelected = [val];
|
||||||
|
} else {
|
||||||
|
newSelected = selectedValues.includes(val)
|
||||||
|
? selectedValues.filter((v) => v !== val)
|
||||||
|
: [...selectedValues, val];
|
||||||
|
}
|
||||||
|
setSelectedValues(newSelected);
|
||||||
|
onChange?.(
|
||||||
|
mode === "single"
|
||||||
|
? newSelected.length > 0
|
||||||
|
? newSelected[0]
|
||||||
|
: undefined
|
||||||
|
: newSelected
|
||||||
|
);
|
||||||
|
if (mode === "single") {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setSearchText("");
|
setSearchText("");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setSelectedValue(undefined);
|
setSelectedValues([]);
|
||||||
onChange?.(undefined);
|
onChange?.(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,13 +121,26 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
? colors.backgroundSecondary
|
? colors.backgroundSecondary
|
||||||
: colors.surface;
|
: colors.surface;
|
||||||
|
|
||||||
|
let displayText = placeholder;
|
||||||
|
if (selectedValues.length > 0) {
|
||||||
|
if (mode === "single") {
|
||||||
|
const opt = options.find((o) => o.value === selectedValues[0]);
|
||||||
|
displayText = opt?.label || placeholder;
|
||||||
|
} else {
|
||||||
|
const labels = selectedValues
|
||||||
|
.map((v) => options.find((o) => o.value === v)?.label)
|
||||||
|
.filter(Boolean);
|
||||||
|
displayText = labels.join(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.wrapper}>
|
<View style={styles.wrapper}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
{
|
{
|
||||||
height: sz.height,
|
height: Math.max(sz.height, textHeight + 16), // Add padding
|
||||||
paddingHorizontal: sz.paddingHorizontal,
|
paddingHorizontal: sz.paddingHorizontal,
|
||||||
backgroundColor: selectBackgroundColor,
|
backgroundColor: selectBackgroundColor,
|
||||||
borderColor: disabled ? colors.border : colors.primary,
|
borderColor: disabled ? colors.border : colors.primary,
|
||||||
@@ -129,19 +163,19 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
fontSize: sz.fontSize,
|
fontSize: sz.fontSize,
|
||||||
color: disabled
|
color: disabled
|
||||||
? colors.textSecondary
|
? colors.textSecondary
|
||||||
: selectedValue
|
: selectedValues.length > 0
|
||||||
? colors.text
|
? colors.text
|
||||||
: colors.textSecondary,
|
: colors.textSecondary,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
numberOfLines={1}
|
onLayout={(e) => setTextHeight(e.nativeEvent.layout.height)}
|
||||||
>
|
>
|
||||||
{selectedOption?.label || placeholder}
|
{displayText}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.suffix}>
|
<View style={styles.suffix}>
|
||||||
{allowClear && selectedValue && !loading ? (
|
{allowClear && selectedValues.length > 0 && !loading ? (
|
||||||
<TouchableOpacity onPress={handleClear} style={styles.icon}>
|
<TouchableOpacity onPress={handleClear} style={styles.icon}>
|
||||||
<AntDesign name="close" size={16} color={colors.textSecondary} />
|
<AntDesign name="close" size={16} color={colors.textSecondary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -193,7 +227,7 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
borderBottomColor: colors.separator,
|
borderBottomColor: colors.separator,
|
||||||
},
|
},
|
||||||
item.disabled && styles.optionDisabled,
|
item.disabled && styles.optionDisabled,
|
||||||
selectedValue === item.value && {
|
selectedValues.includes(item.value) && {
|
||||||
backgroundColor: colors.primary + "20", // Add transparency to primary color
|
backgroundColor: colors.primary + "20", // Add transparency to primary color
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -209,7 +243,7 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
item.disabled && {
|
item.disabled && {
|
||||||
color: colors.textSecondary,
|
color: colors.textSecondary,
|
||||||
},
|
},
|
||||||
selectedValue === item.value && {
|
selectedValues.includes(item.value) && {
|
||||||
color: colors.primary,
|
color: colors.primary,
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
@@ -217,7 +251,7 @@ const Select: React.FC<SelectProps> = ({
|
|||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Text>
|
</Text>
|
||||||
{selectedValue === item.value && (
|
{selectedValues.includes(item.value) && (
|
||||||
<AntDesign name="check" size={16} color={colors.primary} />
|
<AntDesign name="check" size={16} color={colors.primary} />
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|||||||
@@ -91,8 +91,14 @@ const ShipSearchForm = (props: ShipSearchFormProps) => {
|
|||||||
reset({
|
reset({
|
||||||
ship_name: props.initialValues.ship_name || "",
|
ship_name: props.initialValues.ship_name || "",
|
||||||
reg_number: props.initialValues.reg_number || "",
|
reg_number: props.initialValues.reg_number || "",
|
||||||
ship_length: [0, 100],
|
ship_length: [
|
||||||
ship_power: [0, 100000],
|
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 || "",
|
ship_type: props.initialValues.ship_type || "",
|
||||||
alarm_list: props.initialValues.alarm_list || "",
|
alarm_list: props.initialValues.alarm_list || "",
|
||||||
ship_group_id: props.initialValues.ship_group_id || "",
|
ship_group_id: props.initialValues.ship_group_id || "",
|
||||||
@@ -129,7 +135,9 @@ const ShipSearchForm = (props: ShipSearchFormProps) => {
|
|||||||
|
|
||||||
const onSubmitForm = (data: SearchShipResponse) => {
|
const onSubmitForm = (data: SearchShipResponse) => {
|
||||||
props.onSubmit?.(data);
|
props.onSubmit?.(data);
|
||||||
props.onClose();
|
console.log("Data: ", data);
|
||||||
|
|
||||||
|
// props.onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
@@ -317,6 +325,7 @@ const ShipSearchForm = (props: ShipSearchFormProps) => {
|
|||||||
value: type.id || 0,
|
value: type.id || 0,
|
||||||
}))}
|
}))}
|
||||||
placeholder="Chọn loại tàu"
|
placeholder="Chọn loại tàu"
|
||||||
|
mode="multiple"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
@@ -339,6 +348,7 @@ const ShipSearchForm = (props: ShipSearchFormProps) => {
|
|||||||
value: type.value || "",
|
value: type.value || "",
|
||||||
}))}
|
}))}
|
||||||
placeholder="Chọn loại cảnh báo"
|
placeholder="Chọn loại cảnh báo"
|
||||||
|
mode="multiple"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
@@ -361,12 +371,14 @@ const ShipSearchForm = (props: ShipSearchFormProps) => {
|
|||||||
value: group.id || "",
|
value: group.id || "",
|
||||||
}))}
|
}))}
|
||||||
placeholder="Chọn đội tàu"
|
placeholder="Chọn đội tàu"
|
||||||
|
mode="multiple"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<View className="h-12"></View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -290,12 +290,13 @@ export default function Slider({
|
|||||||
{Object.entries(marks).map(([key, label]) => {
|
{Object.entries(marks).map(([key, label]) => {
|
||||||
const val = Number(key);
|
const val = Number(key);
|
||||||
const pos = getPositionFromValue(val);
|
const pos = getPositionFromValue(val);
|
||||||
|
const leftPos = Math.max(0, Math.min(pos - 10, width - 40));
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
key={key}
|
key={key}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: pos - 10,
|
left: leftPos,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: "#666",
|
color: "#666",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type TagStateCallbackPayload = {
|
|||||||
isDisconected: boolean; // giữ đúng theo yêu cầu (1 'n')
|
isDisconected: boolean; // giữ đúng theo yêu cầu (1 'n')
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TagStateProps = {
|
type TagStateProps = {
|
||||||
normalCount?: number;
|
normalCount?: number;
|
||||||
warningCount?: number;
|
warningCount?: number;
|
||||||
dangerousCount?: number;
|
dangerousCount?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user