update login, modal detail in tripInfo
This commit is contained in:
91
components/tripInfo/modal/CrewDetailModal.tsx
Normal file
91
components/tripInfo/modal/CrewDetailModal.tsx
Normal 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;
|
||||
222
components/tripInfo/modal/TripCostDetailModal.tsx
Normal file
222
components/tripInfo/modal/TripCostDetailModal.tsx
Normal 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;
|
||||
69
components/tripInfo/modal/style/CrewDetailModal.styles.ts
Normal file
69
components/tripInfo/modal/style/CrewDetailModal.styles.ts
Normal 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;
|
||||
153
components/tripInfo/modal/style/TripCostDetailModal.styles.ts
Normal file
153
components/tripInfo/modal/style/TripCostDetailModal.styles.ts
Normal 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;
|
||||
Reference in New Issue
Block a user