import { queryShipsImage } from "@/controller/DeviceController"; import { useThemeContext } from "@/hooks/use-theme-context"; import { useGroup } from "@/state/use-group"; import { usePort } from "@/state/use-ports"; import { useShipTypes } from "@/state/use-ship-types"; import { Ionicons } from "@expo/vector-icons"; import { fromByteArray } from "base64-js"; import { useEffect, useState } from "react"; import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native"; interface ShipCardProps { ship: Model.Ship; onPress?: () => void; } export default function ShipCard({ ship, onPress }: ShipCardProps) { const { colors } = useThemeContext(); const { ports, getPorts } = usePort(); const { shipTypes, getShipTypes } = useShipTypes(); const [shipImage, setShipImage] = useState(null); const { groups, getUserGroups, getChildrenOfGroups, childrenOfGroups } = useGroup(); useEffect(() => { if (ports === null) { getPorts(); } }, [ports, getPorts]); useEffect(() => { if (!shipTypes || shipTypes.length === 0) { getShipTypes(); } }, [shipTypes, getShipTypes]); useEffect(() => { if (groups === null) { getUserGroups(); } }, [groups, getUserGroups]); useEffect(() => { if (groups && ship.ship_group_id) { const groupId = groups?.groups?.[0]?.id || ""; // childrenOfGroups is initialised as null in the store; check for null to fetch once if (groupId && childrenOfGroups == null) { getChildrenOfGroups(groupId); } } }, [groups, childrenOfGroups, getChildrenOfGroups]); // Themed styles useEffect(() => { let mounted = true; const loadShipImage = async () => { try { const resp = await queryShipsImage(ship.id || ""); const contentType = resp.headers["content-type"] || "image/jpeg"; const uint8 = new Uint8Array(resp.data); // ArrayBuffer -> Uint8Array const base64 = fromByteArray(uint8); // base64-js const uri = `data:${contentType};base64,${base64}`; if (!mounted) return; // assign received value to state if present; adapt to actual resp shape as needed setShipImage(uri); } catch (error) { // console.log("Error when get image: ", error); } }; loadShipImage(); return () => { mounted = false; }; }, [ship]); const themedStyles = { card: { backgroundColor: colors.card, shadowColor: colors.text, }, title: { color: colors.text, }, subtitle: { color: colors.textSecondary, }, label: { color: colors.textSecondary, }, value: { color: colors.text, }, divider: { backgroundColor: colors.separator, }, badge: { backgroundColor: colors.primary + "15", borderColor: colors.primary, }, badgeText: { color: colors.primary, }, infoBox: { backgroundColor: colors.primary + "10", }, infoIcon: { color: colors.primary, }, }; // ============ IMAGE VARIANT ============ if (shipImage) { return ( {/* Image Section */} {shipImage ? ( ) : ( )} {/* Ship Type Badge */} {shipTypes && ( {shipTypes.find((type) => type.id === ship.ship_type)?.name || "Unknown"} )} {/* Info Section */} {/* Title & Registration */} {ship.name || "Unknown Ship"} {ship.reg_number || "-"} {childrenOfGroups && ( {childrenOfGroups.groups?.find( (group) => group?.metadata?.code === ship.province_code )?.name || "-"} )} {/* Info Grid */} port.id === ship.home_port) ?.name || "-" : "-" } themedStyles={themedStyles} /> ); } // ============ COMPACT VARIANT ============ return ( {/* Header */} {ship.name || "Unknown Ship"} {shipTypes.find((type) => type.id === ship.ship_type)?.name || "Unknown"} {ship.reg_number || "-"} {/* Info Grid */} port.id === ship.home_port)?.name || "-" : "-" } themedStyles={themedStyles} /> group?.metadata?.code === ship.province_code )?.name || "-" } themedStyles={themedStyles} /> {/* Footer - IMO & MMSI */} {(ship.imo_number || ship.mmsi_number) && ( <> {ship.imo_number && ( IMO Number: {ship.imo_number} )} {ship.mmsi_number && ( MMSI Number: {ship.mmsi_number} )} )} ); } // ============ SUB-COMPONENTS ============ interface InfoBoxProps { icon: keyof typeof Ionicons.glyphMap; label: string; value: string; themedStyles: any; } function InfoBox({ icon, label, value, themedStyles }: InfoBoxProps) { return ( {label} {value} ); } function CompactInfoBox({ icon, label, value, themedStyles }: InfoBoxProps) { return ( {label} {value} ); } // ============ STYLES ============ const styles = StyleSheet.create({ // === IMAGE VARIANT === imageCard: { borderRadius: 16, overflow: "hidden", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 3, marginVertical: 8, marginHorizontal: 16, boxShadow: "0 2px 8px rgba(0,0,0,0.1)", }, imageContainer: { height: 180, position: "relative", }, shipImage: { width: "100%", height: "100%", }, imagePlaceholder: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", }, typeBadge: { position: "absolute", top: 12, left: 12, flexDirection: "row", alignItems: "center", backgroundColor: "rgba(59, 130, 246, 0.9)", paddingHorizontal: 10, paddingVertical: 6, borderRadius: 8, gap: 6, }, typeBadgeText: { color: "#fff", fontSize: 12, fontWeight: "600", }, imageCardContent: { padding: 16, }, imageCardTitle: { fontSize: 18, fontWeight: "700", marginBottom: 8, }, regRow: { flexDirection: "row", alignItems: "center", gap: 12, marginBottom: 16, }, regBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, borderWidth: 1, }, regBadgeText: { fontSize: 12, fontWeight: "600", }, locationRow: { flexDirection: "row", alignItems: "center", gap: 4, }, locationText: { fontSize: 13, }, imageInfoGrid: { flexDirection: "row", flexWrap: "wrap", gap: 12, }, infoBox: { width: "47%", flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, }, infoLabel: { fontSize: 12, }, infoValue: { fontSize: 14, fontWeight: "600", marginLeft: "auto", }, // === COMPACT VARIANT === compactCard: { borderRadius: 16, padding: 16, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8, elevation: 2, marginVertical: 8, marginHorizontal: 16, }, compactHeader: { flexDirection: "row", alignItems: "center", marginBottom: 16, }, shipIcon: { width: 48, height: 48, borderRadius: 12, alignItems: "center", justifyContent: "center", }, compactHeaderText: { flex: 1, marginLeft: 12, }, compactTitle: { fontSize: 16, fontWeight: "700", }, compactSubtitle: { fontSize: 13, marginTop: 2, }, compactInfoGrid: { flexDirection: "row", flexWrap: "wrap", gap: 10, }, compactInfoBox: { flexDirection: "row", alignItems: "center", paddingHorizontal: 12, paddingVertical: 10, borderRadius: 10, gap: 8, minWidth: "47%", flexGrow: 1, }, compactInfoText: { flex: 1, }, compactInfoLabel: { fontSize: 11, }, compactInfoValue: { fontSize: 14, fontWeight: "600", }, divider: { height: 1, marginVertical: 12, }, footerInfo: { gap: 6, }, footerRow: { flexDirection: "row", justifyContent: "space-between", }, footerLabel: { fontSize: 13, }, footerValue: { fontSize: 13, fontWeight: "500", }, });