181 lines
5.4 KiB
TypeScript
181 lines
5.4 KiB
TypeScript
import { AlarmData } from "@/app/(tabs)";
|
|
import { ThemedText } from "@/components/themed-text";
|
|
import { useAppTheme } from "@/hooks/use-app-theme";
|
|
import { formatTimestamp } from "@/services/time_service";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useCallback } from "react";
|
|
import { FlatList, TouchableOpacity, View } from "react-native";
|
|
|
|
// ============ Types ============
|
|
type AlarmType = "approaching" | "entered" | "fishing";
|
|
|
|
interface AlarmCardProps {
|
|
alarm: AlarmData;
|
|
onPress?: () => void;
|
|
}
|
|
|
|
// ============ Config ============
|
|
const getAlarmConfig = (isDark: boolean) => ({
|
|
entered: {
|
|
icon: "warning" as keyof typeof Ionicons.glyphMap,
|
|
label: "Xâm nhập",
|
|
bgColor: isDark ? "rgba(220, 38, 38, 0.15)" : "#FEF2F2",
|
|
borderColor: isDark ? "rgba(220, 38, 38, 0.3)" : "#FECACA",
|
|
iconBgColor: isDark ? "rgba(220, 38, 38, 0.25)" : "#FEE2E2",
|
|
iconColor: isDark ? "#F87171" : "#DC2626",
|
|
labelColor: isDark ? "#F87171" : "#DC2626",
|
|
},
|
|
approaching: {
|
|
icon: "alert-circle" as keyof typeof Ionicons.glyphMap,
|
|
label: "Tiếp cận",
|
|
bgColor: isDark ? "rgba(217, 119, 6, 0.15)" : "#FFFBEB",
|
|
borderColor: isDark ? "rgba(217, 119, 6, 0.3)" : "#FDE68A",
|
|
iconBgColor: isDark ? "rgba(217, 119, 6, 0.25)" : "#FEF3C7",
|
|
iconColor: isDark ? "#FBBF24" : "#D97706",
|
|
labelColor: isDark ? "#FBBF24" : "#D97706",
|
|
},
|
|
fishing: {
|
|
icon: "fish" as keyof typeof Ionicons.glyphMap,
|
|
label: "Đánh bắt",
|
|
bgColor: isDark ? "rgba(234, 88, 12, 0.15)" : "#FFF7ED",
|
|
borderColor: isDark ? "rgba(234, 88, 12, 0.3)" : "#FED7AA",
|
|
iconBgColor: isDark ? "rgba(234, 88, 12, 0.25)" : "#FFEDD5",
|
|
iconColor: isDark ? "#FB923C" : "#EA580C",
|
|
labelColor: isDark ? "#FB923C" : "#EA580C",
|
|
},
|
|
});
|
|
|
|
// ============ AlarmCard Component ============
|
|
const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => {
|
|
const { colors, utils } = useAppTheme();
|
|
const isDark = utils.isDark;
|
|
const alarmConfig = getAlarmConfig(isDark);
|
|
const config = alarmConfig[alarm.type];
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
onPress={onPress}
|
|
activeOpacity={0.7}
|
|
style={{
|
|
borderRadius: 16,
|
|
padding: 16,
|
|
backgroundColor: config.bgColor,
|
|
borderWidth: 1,
|
|
borderColor: config.borderColor,
|
|
}}
|
|
>
|
|
<View className="flex-row items-start gap-3">
|
|
{/* Icon Container */}
|
|
<View
|
|
style={{
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 12,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
backgroundColor: config.iconBgColor,
|
|
}}
|
|
>
|
|
<Ionicons name={config.icon} size={24} color={config.iconColor} />
|
|
</View>
|
|
|
|
{/* Content */}
|
|
<View className="flex-1">
|
|
{/* Header: Ship name + Badge */}
|
|
<View className="flex-row items-center justify-between mb-1">
|
|
<ThemedText
|
|
style={{
|
|
fontSize: 16,
|
|
fontWeight: "700",
|
|
color: colors.text,
|
|
flex: 1,
|
|
marginRight: 8,
|
|
}}
|
|
>
|
|
{alarm.ship_name || alarm.thing_id}
|
|
</ThemedText>
|
|
<View
|
|
style={{
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 9999,
|
|
backgroundColor: config.iconBgColor,
|
|
}}
|
|
>
|
|
<ThemedText
|
|
style={{
|
|
fontSize: 12,
|
|
fontWeight: "600",
|
|
color: config.labelColor,
|
|
}}
|
|
>
|
|
{config.label}
|
|
</ThemedText>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Zone Info */}
|
|
<ThemedText
|
|
style={{
|
|
fontSize: 12,
|
|
color: colors.textSecondary,
|
|
marginBottom: 8,
|
|
}}
|
|
numberOfLines={2}
|
|
>
|
|
{alarm.zone.message || alarm.zone.zone_name}
|
|
</ThemedText>
|
|
|
|
{/* Footer: Zone ID + Time */}
|
|
<View className="flex-row items-center justify-between">
|
|
<View className="flex-row items-center gap-1">
|
|
<Ionicons name="time-outline" size={20} color={colors.icon} />
|
|
<ThemedText
|
|
style={{
|
|
fontSize: 12,
|
|
color: colors.textSecondary,
|
|
}}
|
|
>
|
|
{formatTimestamp(alarm.zone.gps_time)}
|
|
</ThemedText>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
// ============ Main Component ============
|
|
interface AlarmListProps {
|
|
data: AlarmData[];
|
|
onPress?: (alarm: AlarmData) => void;
|
|
}
|
|
|
|
export default function AlarmList({ data, onPress }: AlarmListProps) {
|
|
const renderItem = useCallback(
|
|
({ item }: { item: AlarmData }) => (
|
|
<AlarmCard alarm={item} onPress={() => onPress?.(item)} />
|
|
),
|
|
[onPress]
|
|
);
|
|
|
|
const keyExtractor = useCallback(
|
|
(item: AlarmData, index: number) => `${item.thing_id}-${index}`,
|
|
[]
|
|
);
|
|
|
|
const ItemSeparator = useCallback(() => <View className="h-3" />, []);
|
|
|
|
return (
|
|
<FlatList
|
|
data={data}
|
|
renderItem={renderItem}
|
|
keyExtractor={keyExtractor}
|
|
ItemSeparatorComponent={ItemSeparator}
|
|
contentContainerStyle={{ padding: 16 }}
|
|
showsVerticalScrollIndicator={false}
|
|
/>
|
|
);
|
|
}
|