Cập nhật theme cho cảnh báo, giám sát
This commit is contained in:
@@ -10,6 +10,7 @@ import ShipSearchForm, {
|
|||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
import { EVENT_SEARCH_THINGS, IOS_PLATFORM, LIGHT_THEME } from "@/constants";
|
import { EVENT_SEARCH_THINGS, IOS_PLATFORM, LIGHT_THEME } from "@/constants";
|
||||||
import { queryBanzoneById } from "@/controller/MapController";
|
import { queryBanzoneById } from "@/controller/MapController";
|
||||||
|
import { useAppTheme } from "@/hooks/use-app-theme";
|
||||||
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";
|
||||||
@@ -69,6 +70,7 @@ export default function HomeScreen() {
|
|||||||
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 { colors } = useAppTheme();
|
||||||
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<
|
const [shipSearchFormData, setShipSearchFormData] = useState<
|
||||||
@@ -85,7 +87,7 @@ export default function HomeScreen() {
|
|||||||
|
|
||||||
// Alarm list animation
|
// Alarm list animation
|
||||||
const screenHeight = Dimensions.get("window").height;
|
const screenHeight = Dimensions.get("window").height;
|
||||||
const alarmListHeight = Math.round(screenHeight * 0.3);
|
const alarmListHeight = Math.round(screenHeight * 0.4);
|
||||||
const alarmTranslateY = useRef(new Animated.Value(alarmListHeight)).current;
|
const alarmTranslateY = useRef(new Animated.Value(alarmListHeight)).current;
|
||||||
const alarmOpacity = useRef(new Animated.Value(0)).current;
|
const alarmOpacity = useRef(new Animated.Value(0)).current;
|
||||||
const uiAnim = useRef(new Animated.Value(1)).current; // 1 visible, 0 hidden
|
const uiAnim = useRef(new Animated.Value(1)).current; // 1 visible, 0 hidden
|
||||||
@@ -693,6 +695,7 @@ export default function HomeScreen() {
|
|||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
|
color: colors.text,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Danh sách tàu thuyền
|
Danh sách tàu thuyền
|
||||||
@@ -735,7 +738,7 @@ export default function HomeScreen() {
|
|||||||
style={{
|
style={{
|
||||||
width: "50%",
|
width: "50%",
|
||||||
height: 1,
|
height: 1,
|
||||||
backgroundColor: "#E5E7EB",
|
backgroundColor: colors.separator,
|
||||||
marginVertical: 8,
|
marginVertical: 8,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
}}
|
}}
|
||||||
@@ -766,22 +769,36 @@ export default function HomeScreen() {
|
|||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className="bg-white rounded-t-3xl shadow-md overflow-hidden h-full z-50">
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderTopLeftRadius: 24,
|
||||||
|
borderTopRightRadius: 24,
|
||||||
|
overflow: "hidden",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<View className="flex-row items-center justify-between px-4 py-2">
|
<View className="flex-row items-center justify-between px-4 py-2">
|
||||||
<Text className="text-lg font-semibold">
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: "600",
|
||||||
|
color: colors.text,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Danh sách cảnh báo
|
Danh sách cảnh báo
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => closeAlarmList()}
|
onPress={() => closeAlarmList()}
|
||||||
style={{ padding: 6 }}
|
style={{ padding: 6 }}
|
||||||
>
|
>
|
||||||
<Ionicons name="close" size={20} color="#374151" />
|
<Ionicons name="close" size={20} color={colors.icon} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: "#F9FAFB",
|
backgroundColor: colors.backgroundSecondary,
|
||||||
zIndex: 50,
|
zIndex: 50,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -84,7 +84,12 @@ const WarningScreen = () => {
|
|||||||
});
|
});
|
||||||
const slice = resp.data?.alarms ?? [];
|
const slice = resp.data?.alarms ?? [];
|
||||||
|
|
||||||
setAlarms((prev) => (append ? [...prev, ...slice] : slice));
|
// Sort alarms by level descending (higher level first: SOS > Danger > Warning > Info)
|
||||||
|
const sortedSlice = [...slice].sort(
|
||||||
|
(a, b) => (b.level ?? 0) - (a.level ?? 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
setAlarms((prev) => (append ? [...prev, ...sortedSlice] : sortedSlice));
|
||||||
setOffset(nextOffset);
|
setOffset(nextOffset);
|
||||||
setHasMore(nextOffset + PAGE_SIZE < resp.data?.total!);
|
setHasMore(nextOffset + PAGE_SIZE < resp.data?.total!);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useAppTheme } from "@/hooks/use-app-theme";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
|
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
@@ -36,6 +37,7 @@ export default function DraggablePanel({
|
|||||||
}: DraggablePanelProps) {
|
}: DraggablePanelProps) {
|
||||||
const { height: screenHeight } = useWindowDimensions();
|
const { height: screenHeight } = useWindowDimensions();
|
||||||
const bottomOffset = useBottomTabBarHeight();
|
const bottomOffset = useBottomTabBarHeight();
|
||||||
|
const { colors } = useAppTheme();
|
||||||
|
|
||||||
const minHeight = screenHeight * minHeightPct;
|
const minHeight = screenHeight * minHeightPct;
|
||||||
const maxHeight = screenHeight * maxHeightPct;
|
const maxHeight = screenHeight * maxHeightPct;
|
||||||
@@ -141,7 +143,12 @@ export default function DraggablePanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.panelContainer, animatedStyle]}>
|
<Animated.View style={[styles.panelContainer, animatedStyle]}>
|
||||||
<View style={[styles.panel, { height: maxHeight }]}>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.panel,
|
||||||
|
{ height: maxHeight, backgroundColor: colors.surface },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{/* Header với drag handle và nút toggle */}
|
{/* Header với drag handle và nút toggle */}
|
||||||
<GestureDetector gesture={panGesture}>
|
<GestureDetector gesture={panGesture}>
|
||||||
<Pressable onPress={togglePanel} style={styles.header}>
|
<Pressable onPress={togglePanel} style={styles.header}>
|
||||||
@@ -151,7 +158,7 @@ export default function DraggablePanel({
|
|||||||
style={styles.toggleButton}
|
style={styles.toggleButton}
|
||||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
>
|
>
|
||||||
<Ionicons name={iconName} size={24} color="#666" />
|
<Ionicons name={iconName} size={24} color={colors.icon} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</GestureDetector>
|
</GestureDetector>
|
||||||
|
|||||||
@@ -34,37 +34,51 @@ export const AlarmCard: React.FC<AlarmCardProps> = ({ alarm, onReload }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Determine level and colors based on alarm level
|
// Determine level and colors based on alarm level
|
||||||
|
// 0: Thông tin (Xanh lá), 1: Cảnh báo (Vàng), 2: Nguy hiểm (Đỏ), 3: Khẩn cấp SOS (Tím)
|
||||||
const getAlarmConfig = (level?: number) => {
|
const getAlarmConfig = (level?: number) => {
|
||||||
if (level === 3) {
|
switch (level) {
|
||||||
// Danger - Red
|
case 3:
|
||||||
|
// SOS - Khẩn cấp (Purple)
|
||||||
return {
|
return {
|
||||||
level: 3,
|
level: 3,
|
||||||
icon: "warning" as const,
|
icon: "alert" as const,
|
||||||
bgColor: "#fee2e2",
|
bgColor: colors.card,
|
||||||
borderColor: "#DC0E0E",
|
borderColor: "#9333EA", // Purple
|
||||||
iconColor: "#dc2626",
|
iconColor: "#9333EA",
|
||||||
statusBg: "#dcfce7",
|
statusBg: "#dcfce7",
|
||||||
statusText: "#166534",
|
statusText: "#166534",
|
||||||
};
|
};
|
||||||
} else if (level === 2) {
|
case 2:
|
||||||
// Caution - Yellow/Orange
|
// Nguy hiểm - Đỏ
|
||||||
return {
|
return {
|
||||||
level: 2,
|
level: 2,
|
||||||
|
icon: "warning" as const,
|
||||||
|
bgColor: colors.card,
|
||||||
|
borderColor: colors.error,
|
||||||
|
iconColor: colors.error,
|
||||||
|
statusBg: "#dcfce7",
|
||||||
|
statusText: "#166534",
|
||||||
|
};
|
||||||
|
case 1:
|
||||||
|
// Cảnh báo - Vàng
|
||||||
|
return {
|
||||||
|
level: 1,
|
||||||
icon: "alert-circle" as const,
|
icon: "alert-circle" as const,
|
||||||
bgColor: "#fef3c7",
|
bgColor: colors.card,
|
||||||
borderColor: "#FF6C0C",
|
borderColor: colors.warning,
|
||||||
iconColor: "#d97706",
|
iconColor: colors.warning,
|
||||||
statusBg: "#fef08a",
|
statusBg: "#fef08a",
|
||||||
statusText: "#713f12",
|
statusText: "#713f12",
|
||||||
};
|
};
|
||||||
} else {
|
case 0:
|
||||||
// Info - Green
|
default:
|
||||||
|
// Thông tin - Xanh lá
|
||||||
return {
|
return {
|
||||||
level: 1,
|
level: 0,
|
||||||
icon: "information-circle" as const,
|
icon: "information-circle" as const,
|
||||||
bgColor: "#fffefe",
|
bgColor: colors.card,
|
||||||
borderColor: "#FF937E",
|
borderColor: colors.success,
|
||||||
iconColor: "#FF937E",
|
iconColor: colors.success,
|
||||||
statusBg: "#dcfce7",
|
statusBg: "#dcfce7",
|
||||||
statusText: "#166534",
|
statusText: "#166534",
|
||||||
};
|
};
|
||||||
@@ -217,14 +231,16 @@ export const AlarmCard: React.FC<AlarmCardProps> = ({ alarm, onReload }) => {
|
|||||||
style={[
|
style={[
|
||||||
styles.statusBadge,
|
styles.statusBadge,
|
||||||
{
|
{
|
||||||
backgroundColor: alarm.confirmed ? "#8FD14F" : "#EEEEEE",
|
backgroundColor: alarm.confirmed
|
||||||
|
? "#8FD14F"
|
||||||
|
: colors.surfaceSecondary,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.statusText,
|
styles.statusText,
|
||||||
{ color: alarm.confirmed ? "#166534" : "black" },
|
{ color: alarm.confirmed ? "#166534" : colors.text },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{alarm.confirmed ? "Đã xác nhận" : "Chờ xác nhận"}
|
{alarm.confirmed ? "Đã xác nhận" : "Chờ xác nhận"}
|
||||||
@@ -262,7 +278,10 @@ export const AlarmCard: React.FC<AlarmCardProps> = ({ alarm, onReload }) => {
|
|||||||
Nhập ghi chú xác nhận
|
Nhập ghi chú xác nhận
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { color: colors.text }]}
|
style={[
|
||||||
|
styles.input,
|
||||||
|
{ color: colors.text, borderColor: colors.border },
|
||||||
|
]}
|
||||||
placeholder="Nhập ghi chú"
|
placeholder="Nhập ghi chú"
|
||||||
placeholderTextColor={colors.textSecondary}
|
placeholderTextColor={colors.textSecondary}
|
||||||
multiline
|
multiline
|
||||||
@@ -272,14 +291,20 @@ export const AlarmCard: React.FC<AlarmCardProps> = ({ alarm, onReload }) => {
|
|||||||
/>
|
/>
|
||||||
<View style={styles.modalActions}>
|
<View style={styles.modalActions}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.modalButton, styles.cancelButton]}
|
style={[
|
||||||
|
styles.modalButton,
|
||||||
|
styles.cancelButton,
|
||||||
|
{ backgroundColor: colors.surfaceSecondary },
|
||||||
|
]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
setNote("");
|
setNote("");
|
||||||
}}
|
}}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
>
|
>
|
||||||
<Text style={styles.cancelText}>Hủy</Text>
|
<Text style={[styles.cancelText, { color: colors.text }]}>
|
||||||
|
Hủy
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AlarmData } from "@/app/(tabs)";
|
import { AlarmData } from "@/app/(tabs)";
|
||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
|
import { useAppTheme } from "@/hooks/use-app-theme";
|
||||||
import { formatTimestamp } from "@/services/time_service";
|
import { formatTimestamp } from "@/services/time_service";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
@@ -14,61 +15,66 @@ interface AlarmCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============ Config ============
|
// ============ Config ============
|
||||||
const ALARM_CONFIG: Record<
|
const getAlarmConfig = (isDark: boolean) => ({
|
||||||
AlarmType,
|
|
||||||
{
|
|
||||||
icon: keyof typeof Ionicons.glyphMap;
|
|
||||||
label: string;
|
|
||||||
bgColor: string;
|
|
||||||
borderColor: string;
|
|
||||||
iconBgColor: string;
|
|
||||||
iconColor: string;
|
|
||||||
labelColor: string;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
entered: {
|
entered: {
|
||||||
icon: "warning",
|
icon: "warning" as keyof typeof Ionicons.glyphMap,
|
||||||
label: "Xâm nhập",
|
label: "Xâm nhập",
|
||||||
bgColor: "bg-red-50",
|
bgColor: isDark ? "rgba(220, 38, 38, 0.15)" : "#FEF2F2",
|
||||||
borderColor: "border-red-200",
|
borderColor: isDark ? "rgba(220, 38, 38, 0.3)" : "#FECACA",
|
||||||
iconBgColor: "bg-red-100",
|
iconBgColor: isDark ? "rgba(220, 38, 38, 0.25)" : "#FEE2E2",
|
||||||
iconColor: "#DC2626",
|
iconColor: isDark ? "#F87171" : "#DC2626",
|
||||||
labelColor: "text-red-600",
|
labelColor: isDark ? "#F87171" : "#DC2626",
|
||||||
},
|
},
|
||||||
approaching: {
|
approaching: {
|
||||||
icon: "alert-circle",
|
icon: "alert-circle" as keyof typeof Ionicons.glyphMap,
|
||||||
label: "Tiếp cận",
|
label: "Tiếp cận",
|
||||||
bgColor: "bg-amber-50",
|
bgColor: isDark ? "rgba(217, 119, 6, 0.15)" : "#FFFBEB",
|
||||||
borderColor: "border-amber-200",
|
borderColor: isDark ? "rgba(217, 119, 6, 0.3)" : "#FDE68A",
|
||||||
iconBgColor: "bg-amber-100",
|
iconBgColor: isDark ? "rgba(217, 119, 6, 0.25)" : "#FEF3C7",
|
||||||
iconColor: "#D97706",
|
iconColor: isDark ? "#FBBF24" : "#D97706",
|
||||||
labelColor: "text-amber-600",
|
labelColor: isDark ? "#FBBF24" : "#D97706",
|
||||||
},
|
},
|
||||||
fishing: {
|
fishing: {
|
||||||
icon: "fish",
|
icon: "fish" as keyof typeof Ionicons.glyphMap,
|
||||||
label: "Đánh bắt",
|
label: "Đánh bắt",
|
||||||
bgColor: "bg-orange-50",
|
bgColor: isDark ? "rgba(234, 88, 12, 0.15)" : "#FFF7ED",
|
||||||
borderColor: "border-orange-200",
|
borderColor: isDark ? "rgba(234, 88, 12, 0.3)" : "#FED7AA",
|
||||||
iconBgColor: "bg-orange-100",
|
iconBgColor: isDark ? "rgba(234, 88, 12, 0.25)" : "#FFEDD5",
|
||||||
iconColor: "#EA580C",
|
iconColor: isDark ? "#FB923C" : "#EA580C",
|
||||||
labelColor: "text-orange-600",
|
labelColor: isDark ? "#FB923C" : "#EA580C",
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
// ============ AlarmCard Component ============
|
// ============ AlarmCard Component ============
|
||||||
const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => {
|
const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => {
|
||||||
const config = ALARM_CONFIG[alarm.type];
|
const { colors, utils } = useAppTheme();
|
||||||
|
const isDark = utils.isDark;
|
||||||
|
const alarmConfig = getAlarmConfig(isDark);
|
||||||
|
const config = alarmConfig[alarm.type];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
className={`rounded-2xl p-4 ${config.bgColor} ${config.borderColor} border shadow-sm`}
|
style={{
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: config.bgColor,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: config.borderColor,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<View className="flex-row items-start gap-3">
|
<View className="flex-row items-start gap-3">
|
||||||
{/* Icon Container */}
|
{/* Icon Container */}
|
||||||
<View
|
<View
|
||||||
className={`w-12 h-12 rounded-xl items-center justify-center ${config.iconBgColor}`}
|
style={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 12,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: config.iconBgColor,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Ionicons name={config.icon} size={24} color={config.iconColor} />
|
<Ionicons name={config.icon} size={24} color={config.iconColor} />
|
||||||
</View>
|
</View>
|
||||||
@@ -77,12 +83,31 @@ const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => {
|
|||||||
<View className="flex-1">
|
<View className="flex-1">
|
||||||
{/* Header: Ship name + Badge */}
|
{/* Header: Ship name + Badge */}
|
||||||
<View className="flex-row items-center justify-between mb-1">
|
<View className="flex-row items-center justify-between mb-1">
|
||||||
<ThemedText className="text-base font-bold text-gray-800 flex-1 mr-2">
|
<ThemedText
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: "700",
|
||||||
|
color: colors.text,
|
||||||
|
flex: 1,
|
||||||
|
marginRight: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{alarm.ship_name || alarm.thing_id}
|
{alarm.ship_name || alarm.thing_id}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
<View className={`px-2 py-1 rounded-full ${config.iconBgColor}`}>
|
<View
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 9999,
|
||||||
|
backgroundColor: config.iconBgColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ThemedText
|
<ThemedText
|
||||||
className={`text-xs font-semibold ${config.labelColor}`}
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "600",
|
||||||
|
color: config.labelColor,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{config.label}
|
{config.label}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
@@ -90,15 +115,27 @@ const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Zone Info */}
|
{/* Zone Info */}
|
||||||
<ThemedText className="text-xs text-gray-600 mb-2" numberOfLines={2}>
|
<ThemedText
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
numberOfLines={2}
|
||||||
|
>
|
||||||
{alarm.zone.message || alarm.zone.zone_name}
|
{alarm.zone.message || alarm.zone.zone_name}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
|
|
||||||
{/* Footer: Zone ID + Time */}
|
{/* Footer: Zone ID + Time */}
|
||||||
<View className="flex-row items-center justify-between">
|
<View className="flex-row items-center justify-between">
|
||||||
<View className="flex-row items-center gap-1">
|
<View className="flex-row items-center gap-1">
|
||||||
<Ionicons name="time-outline" size={20} color="#6B7280" />
|
<Ionicons name="time-outline" size={20} color={colors.icon} />
|
||||||
<ThemedText className="text-xs text-gray-500">
|
<ThemedText
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.textSecondary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{formatTimestamp(alarm.zone.gps_time)}
|
{formatTimestamp(alarm.zone.gps_time)}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -13,28 +13,31 @@ interface ShipInfoProps {
|
|||||||
|
|
||||||
const ShipInfo = ({ thingMetadata }: ShipInfoProps) => {
|
const ShipInfo = ({ thingMetadata }: ShipInfoProps) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { colors } = useAppTheme();
|
const { colors, utils } = useAppTheme();
|
||||||
// Định nghĩa màu sắc theo trạng thái
|
const isDark = utils.isDark;
|
||||||
|
|
||||||
|
// Định nghĩa màu sắc theo trạng thái - hỗ trợ cả light và dark theme
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
normal: {
|
normal: {
|
||||||
dotColor: "bg-green-500",
|
dotColor: colors.success,
|
||||||
badgeColor: "bg-green-100",
|
badgeColor: isDark ? "rgba(52, 199, 89, 0.2)" : "#dcfce7",
|
||||||
badgeTextColor: "text-green-700",
|
badgeTextColor: isDark ? "#30D158" : "#15803d",
|
||||||
badgeText: "Bình thường",
|
badgeText: "Bình thường",
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
dotColor: "bg-yellow-500",
|
dotColor: colors.warning,
|
||||||
badgeColor: "bg-yellow-100",
|
badgeColor: isDark ? "rgba(255, 102, 0, 0.2)" : "#fef3c7",
|
||||||
badgeTextColor: "text-yellow-700",
|
badgeTextColor: isDark ? "#ff9500" : "#b45309",
|
||||||
badgeText: "Cảnh báo",
|
badgeText: "Cảnh báo",
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
dotColor: "bg-red-500",
|
dotColor: colors.error,
|
||||||
badgeColor: "bg-red-100",
|
badgeColor: isDark ? "rgba(255, 69, 58, 0.2)" : "#fee2e2",
|
||||||
badgeTextColor: "text-red-700",
|
badgeTextColor: isDark ? "#FF453A" : "#b91c1c",
|
||||||
badgeText: "Nguy hiểm",
|
badgeText: "Nguy hiểm",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getThingStatus = () => {
|
const getThingStatus = () => {
|
||||||
switch (thingMetadata?.state_level) {
|
switch (thingMetadata?.state_level) {
|
||||||
case STATUS_NORMAL:
|
case STATUS_NORMAL:
|
||||||
@@ -67,57 +70,82 @@ const ShipInfo = ({ thingMetadata }: ShipInfoProps) => {
|
|||||||
<View className="flex-row items-center justify-between mb-3">
|
<View className="flex-row items-center justify-between mb-3">
|
||||||
<View className="flex-row items-center gap-2">
|
<View className="flex-row items-center gap-2">
|
||||||
{/* Status dot */}
|
{/* Status dot */}
|
||||||
<View className={`h-3 w-3 rounded-full ${currentStatus.dotColor}`} />
|
<View
|
||||||
|
style={{
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
backgroundColor: currentStatus.dotColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Tên tàu */}
|
{/* Tên tàu */}
|
||||||
<Text className="text-lg font-semibold text-gray-900">
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: "600",
|
||||||
|
color: colors.text,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{thingMetadata?.ship_name}
|
{thingMetadata?.ship_name}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Badge trạng thái */}
|
{/* Badge trạng thái */}
|
||||||
<View className={`px-3 py-1 rounded-full ${currentStatus.badgeColor}`}>
|
<View
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 9999,
|
||||||
|
backgroundColor: currentStatus.badgeColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
className={`text-md font-medium ${currentStatus.badgeTextColor}`}
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "500",
|
||||||
|
color: currentStatus.badgeTextColor,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{currentStatus.badgeText}
|
{currentStatus.badgeText}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Mã tàu */}
|
{/* Tốc độ và hướng */}
|
||||||
{/* <Text className="text-base text-gray-600 mb-2">{shipCode}</Text> */}
|
|
||||||
<View className="flex-row items-center justify-between gap-2 mb-3">
|
<View className="flex-row items-center justify-between gap-2 mb-3">
|
||||||
<View className="flex-row items-center gap-2 w-2/3">
|
<View className="flex-row items-center gap-2 w-2/3">
|
||||||
<Ionicons name="speedometer-outline" size={16} color="#6B7280" />
|
<Ionicons name="speedometer-outline" size={16} color={colors.icon} />
|
||||||
<Text className="text-md text-gray-600">
|
<Text style={{ fontSize: 14, color: colors.textSecondary }}>
|
||||||
{kmhToKnot(gpsData.s || 0)} {t("home.speed_units")}
|
{kmhToKnot(gpsData.s || 0)} {t("home.speed_units")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="flex-row items-start gap-2 w-1/3 ">
|
<View className="flex-row items-start gap-2 w-1/3 ">
|
||||||
<Ionicons name="compass-outline" size={16} color="#6B7280" />
|
<Ionicons name="compass-outline" size={16} color={colors.icon} />
|
||||||
<Text className="text-md text-gray-600">{gpsData.h}°</Text>
|
<Text style={{ fontSize: 14, color: colors.textSecondary }}>
|
||||||
|
{gpsData.h}°
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Tọa độ */}
|
{/* Tọa độ */}
|
||||||
<View className="flex-row items-center justify-between gap-2 mb-2">
|
<View className="flex-row items-center justify-between gap-2 mb-2">
|
||||||
<View className="flex-row items-center gap-2 w-2/3">
|
<View className="flex-row items-center gap-2 w-2/3">
|
||||||
<Ionicons name="location-outline" size={16} color="#6B7280" />
|
<Ionicons name="location-outline" size={16} color={colors.icon} />
|
||||||
<Text className="text-md text-gray-600">
|
<Text style={{ fontSize: 14, color: colors.textSecondary }}>
|
||||||
{convertToDMS(gpsData.lat || 0, true)},
|
{convertToDMS(gpsData.lat || 0, true)},
|
||||||
{convertToDMS(gpsData.lon || 0, false)}
|
{convertToDMS(gpsData.lon || 0, false)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className=" flex-row items-start gap-2 w-1/3 ">
|
<View className=" flex-row items-start gap-2 w-1/3 ">
|
||||||
<Ionicons name="time-outline" size={16} color="#6B7280" />
|
<Ionicons name="time-outline" size={16} color={colors.icon} />
|
||||||
<Text className="text-md text-gray-600">
|
<Text style={{ fontSize: 14, color: colors.textSecondary }}>
|
||||||
{formatRelativeTime(gpsData.t)}
|
{formatRelativeTime(gpsData.t)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{/* <View className="flex">
|
|
||||||
<Description title="Trạng thái" description={thingMetadata?.state} />
|
{/* Trạng thái */}
|
||||||
</View> */}
|
|
||||||
{thingMetadata?.state !== "" && (
|
{thingMetadata?.state !== "" && (
|
||||||
<View className="flex-row items-center gap-2">
|
<View className="flex-row items-center gap-2">
|
||||||
<ThemedText style={{ color: colors.textSecondary, fontSize: 15 }}>
|
<ThemedText style={{ color: colors.textSecondary, fontSize: 15 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user