update login, modal detail in tripInfo

This commit is contained in:
2025-11-01 19:47:45 +07:00
parent eea1482a88
commit 52d2f0f78b
11 changed files with 674 additions and 22 deletions

View File

@@ -0,0 +1,91 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import React from "react";
import { Modal, ScrollView, Text, TouchableOpacity, View } from "react-native";
import styles from "./style/CrewDetailModal.styles";
// ---------------------------
// 🧩 Interface
// ---------------------------
interface CrewMember {
id: string;
maDinhDanh: string;
ten: string;
chucVu: string;
ngaySinh?: string;
cccd?: string;
soDienThoai?: string;
diaChi?: string;
ngayVaoLam?: string;
trinhDoChuyenMon?: string;
bangCap?: string;
tinhTrang?: string;
}
interface CrewDetailModalProps {
visible: boolean;
onClose: () => void;
crewData: CrewMember | null;
}
// ---------------------------
// 👤 Component Modal
// ---------------------------
const CrewDetailModal: React.FC<CrewDetailModalProps> = ({
visible,
onClose,
crewData,
}) => {
if (!crewData) return null;
const infoItems = [
{ label: "Mã định danh", value: crewData.maDinhDanh },
{ label: "Họ và tên", value: crewData.ten },
{ label: "Chức vụ", value: crewData.chucVu },
{ label: "Ngày sinh", value: crewData.ngaySinh || "Chưa cập nhật" },
{ label: "CCCD/CMND", value: crewData.cccd || "Chưa cập nhật" },
{ label: "Số điện thoại", value: crewData.soDienThoai || "Chưa cập nhật" },
{ label: "Địa chỉ", value: crewData.diaChi || "Chưa cập nhật" },
{ label: "Ngày vào làm", value: crewData.ngayVaoLam || "Chưa cập nhật" },
{
label: "Trình độ chuyên môn",
value: crewData.trinhDoChuyenMon || "Chưa cập nhật",
},
{ label: "Bằng cấp", value: crewData.bangCap || "Chưa cập nhật" },
{ label: "Tình trạng", value: crewData.tinhTrang || "Đang làm việc" },
];
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={onClose}
>
<View style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}>Thông tin thuyền viên</Text>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<View style={styles.closeIconButton}>
<IconSymbol name="xmark" size={28} color="#fff" />
</View>
</TouchableOpacity>
</View>
{/* Content */}
<ScrollView style={styles.content}>
<View style={styles.infoCard}>
{infoItems.map((item, index) => (
<View key={index} style={styles.infoRow}>
<Text style={styles.infoLabel}>{item.label}</Text>
<Text style={styles.infoValue}>{item.value}</Text>
</View>
))}
</View>
</ScrollView>
</View>
</Modal>
);
};
export default CrewDetailModal;

View File

@@ -0,0 +1,222 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import React, { useState } from "react";
import {
KeyboardAvoidingView,
Modal,
Platform,
ScrollView,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import styles from "./style/TripCostDetailModal.styles";
// ---------------------------
// 🧩 Interface
// ---------------------------
interface CostItem {
id: string;
loai: string;
soLuong: number;
donVi: string;
chiPhi: number;
tongChiPhi: number;
}
interface TripCostDetailModalProps {
visible: boolean;
onClose: () => void;
data: CostItem[];
}
// ---------------------------
// 💰 Component Modal
// ---------------------------
const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
visible,
onClose,
data,
}) => {
const [isEditing, setIsEditing] = useState(false);
const [editableData, setEditableData] = useState<CostItem[]>(data);
const tongCong = editableData.reduce((sum, item) => sum + item.tongChiPhi, 0);
const handleEdit = () => {
setIsEditing(!isEditing);
};
const handleSave = () => {
setIsEditing(false);
// TODO: Save data to backend
console.log("Saved data:", editableData);
};
const handleCancel = () => {
setIsEditing(false);
setEditableData(data); // Reset to original data
};
const updateItem = (id: string, field: keyof CostItem, value: string) => {
setEditableData((prev) =>
prev.map((item) => {
if (item.id === id) {
const numValue =
field === "loai" || field === "donVi" ? value : Number(value) || 0;
const updated = { ...item, [field]: numValue };
// Recalculate tongChiPhi
if (field === "soLuong" || field === "chiPhi") {
updated.tongChiPhi = updated.soLuong * updated.chiPhi;
}
return updated;
}
return item;
})
);
};
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={onClose}
>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={60}
>
<View style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}>Chi tiết chi phí chuyến đi</Text>
<View style={styles.headerButtons}>
{isEditing ? (
<>
<TouchableOpacity
onPress={handleCancel}
style={styles.cancelButton}
>
<Text style={styles.cancelButtonText}>Hủy</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={handleSave}
style={styles.saveButton}
>
<Text style={styles.saveButtonText}>Lưu</Text>
</TouchableOpacity>
</>
) : (
<TouchableOpacity
onPress={handleEdit}
style={styles.editButton}
>
<View style={styles.editIconButton}>
<IconSymbol
name="pencil"
size={28}
color="#fff"
weight="heavy"
/>
</View>
</TouchableOpacity>
)}
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<View style={styles.closeIconButton}>
<IconSymbol name="xmark" size={28} color="#fff" />
</View>
</TouchableOpacity>
</View>
</View>
{/* Content */}
<ScrollView style={styles.content}>
{editableData.map((item) => (
<View key={item.id} style={styles.itemCard}>
{/* Loại */}
<View style={styles.fieldGroup}>
<Text style={styles.label}>Loại chi phí</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={item.loai}
onChangeText={(value) => updateItem(item.id, "loai", value)}
editable={isEditing}
placeholder="Nhập loại chi phí"
/>
</View>
{/* Số lượng & Đơn vị */}
<View style={styles.rowGroup}>
<View
style={[styles.fieldGroup, { flex: 1, marginRight: 8 }]}
>
<Text style={styles.label}>Số lượng</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={String(item.soLuong)}
onChangeText={(value) =>
updateItem(item.id, "soLuong", value)
}
editable={isEditing}
keyboardType="numeric"
placeholder="0"
/>
</View>
<View style={[styles.fieldGroup, { flex: 1, marginLeft: 8 }]}>
<Text style={styles.label}>Đơn vị</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={item.donVi}
onChangeText={(value) =>
updateItem(item.id, "donVi", value)
}
editable={isEditing}
placeholder="kg, lít..."
/>
</View>
</View>
{/* Chi phí/đơn vị */}
<View style={styles.fieldGroup}>
<Text style={styles.label}>Chi phí/đơn vị (VNĐ)</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={String(item.chiPhi)}
onChangeText={(value) =>
updateItem(item.id, "chiPhi", value)
}
editable={isEditing}
keyboardType="numeric"
placeholder="0"
/>
</View>
{/* Tổng chi phí */}
<View style={styles.fieldGroup}>
<Text style={styles.label}>Tổng chi phí</Text>
<View style={styles.totalContainer}>
<Text style={styles.totalText}>
{item.tongChiPhi.toLocaleString()} VNĐ
</Text>
</View>
</View>
</View>
))}
{/* Footer Total */}
<View style={styles.footerTotal}>
<Text style={styles.footerLabel}>Tổng cộng</Text>
<Text style={styles.footerAmount}>
{tongCong.toLocaleString()} VNĐ
</Text>
</View>
</ScrollView>
</View>
</KeyboardAvoidingView>
</Modal>
);
};
export default TripCostDetailModal;

View File

@@ -0,0 +1,69 @@
import { StyleSheet } from "react-native";
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 20,
paddingTop: 30,
paddingBottom: 16,
backgroundColor: "#fff",
borderBottomWidth: 1,
borderBottomColor: "#eee",
},
title: {
fontSize: 22,
fontWeight: "700",
color: "#000",
flex: 1,
},
closeButton: {
padding: 4,
},
closeIconButton: {
backgroundColor: "#FF3B30",
borderRadius: 10,
padding: 10,
justifyContent: "center",
alignItems: "center",
},
content: {
flex: 1,
padding: 16,
marginBottom: 15,
},
infoCard: {
backgroundColor: "#fff",
borderRadius: 12,
padding: 16,
marginBottom: 20,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 4,
shadowOffset: { width: 0, height: 2 },
elevation: 2,
},
infoRow: {
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
infoLabel: {
fontSize: 13,
fontWeight: "600",
color: "#666",
marginBottom: 6,
},
infoValue: {
fontSize: 16,
color: "#000",
fontWeight: "500",
},
});
export default styles;

View File

@@ -0,0 +1,153 @@
import { StyleSheet } from "react-native";
const styles = StyleSheet.create({
closeIconButton: {
backgroundColor: "#FF3B30",
borderRadius: 10,
padding: 10,
justifyContent: "center",
alignItems: "center",
},
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 20,
paddingTop: 30,
paddingBottom: 16,
backgroundColor: "#fff",
borderBottomWidth: 1,
borderBottomColor: "#eee",
},
title: {
fontSize: 22,
fontWeight: "700",
color: "#000",
flex: 1,
},
headerButtons: {
flexDirection: "row",
alignItems: "center",
gap: 12,
},
editButton: {
padding: 4,
},
editIconButton: {
backgroundColor: "#007AFF",
borderRadius: 10,
padding: 10,
justifyContent: "center",
alignItems: "center",
},
cancelButton: {
paddingHorizontal: 12,
paddingVertical: 6,
},
cancelButtonText: {
color: "#007AFF",
fontSize: 16,
fontWeight: "600",
},
saveButton: {
backgroundColor: "#007AFF",
paddingHorizontal: 16,
paddingVertical: 6,
borderRadius: 6,
},
saveButtonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
closeButton: {
padding: 4,
},
content: {
flex: 1,
padding: 16,
},
itemCard: {
backgroundColor: "#fff",
borderRadius: 12,
padding: 16,
marginBottom: 12,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 4,
shadowOffset: { width: 0, height: 2 },
elevation: 2,
},
fieldGroup: {
marginBottom: 12,
},
rowGroup: {
flexDirection: "row",
marginBottom: 12,
},
label: {
fontSize: 13,
fontWeight: "600",
color: "#666",
marginBottom: 6,
},
input: {
borderWidth: 1,
borderColor: "#007AFF",
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
fontSize: 15,
color: "#000",
backgroundColor: "#fff",
},
inputDisabled: {
borderColor: "#ddd",
backgroundColor: "#f9f9f9",
color: "#666",
},
totalContainer: {
backgroundColor: "#fff5e6",
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
borderWidth: 1,
borderColor: "#ffd699",
},
totalText: {
fontSize: 16,
fontWeight: "700",
color: "#ff6600",
},
footerTotal: {
backgroundColor: "#fff",
borderRadius: 12,
padding: 20,
marginTop: 8,
marginBottom: 50,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.1,
shadowRadius: 4,
shadowOffset: { width: 0, height: 2 },
elevation: 3,
},
footerLabel: {
fontSize: 18,
fontWeight: "700",
color: "#007bff",
},
footerAmount: {
fontSize: 20,
fontWeight: "700",
color: "#ff6600",
},
});
export default styles;