Files

332 lines
8.3 KiB
TypeScript

import React, { useEffect, useState } from "react";
import {
View,
Text,
StyleSheet,
Platform,
Image,
ActivityIndicator,
TouchableOpacity,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useThemeContext } from "@/hooks/use-theme-context";
import { useI18n } from "@/hooks/use-i18n";
import dayjs from "dayjs";
import { queryCrewImage } from "@/controller/CrewController";
import { Buffer } from "buffer";
interface CrewCardProps {
crew: Model.TripCrews;
onEdit?: (crew: Model.TripCrews) => void;
onDelete?: (crew: Model.TripCrews) => void;
}
export default function CrewCard({ crew, onEdit, onDelete }: CrewCardProps) {
const { colors } = useThemeContext();
const { t } = useI18n();
const person = crew.Person;
const joinedDate = crew.joined_at
? dayjs(crew.joined_at).format("DD/MM/YYYY")
: "-";
const leftDate = crew.left_at
? dayjs(crew.left_at).format("DD/MM/YYYY")
: null;
// State for image
const [imageUri, setImageUri] = useState<string | null>(null);
const [imageLoading, setImageLoading] = useState(true);
const [imageError, setImageError] = useState(false);
// Fetch crew image
useEffect(() => {
const fetchImage = async () => {
if (!person?.personal_id) {
setImageLoading(false);
setImageError(true);
return;
}
try {
const response = await queryCrewImage(person.personal_id);
if (response.data) {
// Convert arraybuffer to base64
const base64 = Buffer.from(response.data as ArrayBuffer).toString(
"base64"
);
setImageUri(`data:image/jpeg;base64,${base64}`);
} else {
setImageError(true);
}
} catch (err) {
setImageError(true);
} finally {
setImageLoading(false);
}
};
fetchImage();
}, [person?.personal_id]);
const themedStyles = {
card: {
backgroundColor: colors.card,
borderColor: colors.separator,
},
name: {
color: colors.text,
},
role: {
color: colors.primary,
backgroundColor: colors.primary + "20",
},
label: {
color: colors.textSecondary,
},
value: {
color: colors.text,
},
iconColor: colors.textSecondary,
imagePlaceholder: {
backgroundColor: colors.backgroundSecondary,
},
};
return (
<View style={[styles.card, themedStyles.card]}>
{/* Left Image Section (1/3 width) */}
<View style={[styles.imageSection, themedStyles.imagePlaceholder]}>
{imageLoading ? (
<ActivityIndicator size="small" color={colors.primary} />
) : imageUri && !imageError ? (
<Image
source={{ uri: imageUri }}
style={styles.crewImage}
resizeMode="cover"
/>
) : (
<View style={styles.imagePlaceholder}>
<Ionicons name="person" size={40} color={colors.textSecondary} />
</View>
)}
</View>
{/* Right Content Section (2/3 width) */}
<View style={styles.contentSection}>
{/* Name & Role */}
<View style={styles.header}>
<Text style={[styles.name, themedStyles.name]} numberOfLines={1}>
{person?.name || "-"}
</Text>
<View
style={[
styles.roleBadge,
{ backgroundColor: themedStyles.role.backgroundColor },
]}
>
<Text style={[styles.roleText, { color: themedStyles.role.color }]}>
{crew.role || t("diary.crew.member")}
</Text>
</View>
</View>
{/* Info Grid */}
<View style={styles.infoGrid}>
{/* Phone */}
<View style={styles.infoRow}>
<Ionicons
name="call-outline"
size={14}
color={themedStyles.iconColor}
/>
<Text style={[styles.value, themedStyles.value]} numberOfLines={1}>
{person?.phone || "-"}
</Text>
</View>
{/* Personal ID */}
<View style={styles.infoRow}>
<Ionicons
name="card-outline"
size={14}
color={themedStyles.iconColor}
/>
<Text style={[styles.value, themedStyles.value]} numberOfLines={1}>
{person?.personal_id || "-"}
</Text>
</View>
{/* Joined Date */}
<View style={styles.infoRow}>
<Ionicons
name="calendar-outline"
size={14}
color={themedStyles.iconColor}
/>
<Text style={[styles.value, themedStyles.value]}>{joinedDate}</Text>
</View>
{/* Left Date (only show if exists) */}
{leftDate && (
<View style={styles.infoRow}>
<Ionicons
name="exit-outline"
size={14}
color={themedStyles.iconColor}
/>
<Text style={[styles.value, themedStyles.value]}>{leftDate}</Text>
</View>
)}
</View>
{/* Action Buttons */}
{(onEdit || onDelete) && (
<View style={styles.actionRow}>
{onEdit && (
<TouchableOpacity
style={[
styles.actionButton,
{ backgroundColor: colors.primary + "15" },
]}
onPress={() => onEdit(crew)}
>
<Ionicons
name="pencil-outline"
size={14}
color={colors.primary}
/>
<Text style={[styles.actionText, { color: colors.primary }]}>
{t("common.edit")}
</Text>
</TouchableOpacity>
)}
{onDelete && (
<TouchableOpacity
style={[
styles.actionButton,
{ backgroundColor: (colors.error || "#FF3B30") + "15" },
]}
onPress={() => onDelete(crew)}
>
<Ionicons
name="trash-outline"
size={14}
color={colors.error || "#FF3B30"}
/>
<Text
style={[
styles.actionText,
{ color: colors.error || "#FF3B30" },
]}
>
{t("common.delete")}
</Text>
</TouchableOpacity>
)}
</View>
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
card: {
flexDirection: "row",
borderRadius: 12,
borderWidth: 1,
marginBottom: 12,
overflow: "hidden",
maxHeight: 160,
},
imageSection: {
width: 130,
alignSelf: "stretch",
justifyContent: "center",
alignItems: "center",
},
crewImage: {
width: "100%",
height: "100%",
},
imagePlaceholder: {
flex: 1,
justifyContent: "center",
alignItems: "center",
width: "100%",
},
contentSection: {
flex: 1,
padding: 10,
justifyContent: "center",
},
header: {
marginBottom: 8,
},
name: {
fontSize: 17,
fontWeight: "700",
marginBottom: 4,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
roleBadge: {
alignSelf: "flex-start",
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 10,
},
roleText: {
fontSize: 12,
fontWeight: "600",
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
infoGrid: {
gap: 4,
},
infoRow: {
flexDirection: "row",
alignItems: "center",
gap: 5,
},
value: {
fontSize: 14,
fontWeight: "500",
flex: 1,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
actionRow: {
flexDirection: "row",
gap: 8,
marginTop: 8,
},
actionButton: {
flexDirection: "row",
alignItems: "center",
gap: 4,
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 6,
},
actionText: {
fontSize: 12,
fontWeight: "600",
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
});