diff --git a/app/(tabs)/diary.tsx b/app/(tabs)/diary.tsx index 87de40b..8292afb 100644 --- a/app/(tabs)/diary.tsx +++ b/app/(tabs)/diary.tsx @@ -1,4 +1,12 @@ -import { Platform, ScrollView, StyleSheet, Text, View } from "react-native"; +import { Link } from "expo-router"; +import { + Platform, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; export default function Warning() { @@ -7,6 +15,12 @@ export default function Warning() { Nhật Ký Chuyến Đi + + + + Mở Modal + + @@ -32,4 +46,16 @@ const styles = StyleSheet.create({ default: "System", }), }, + button: { + backgroundColor: "#007AFF", + paddingVertical: 14, + paddingHorizontal: 24, + borderRadius: 8, + marginTop: 20, + }, + buttonText: { + color: "#fff", + fontSize: 16, + fontWeight: "600", + }, }); diff --git a/app/auth/login.tsx b/app/auth/login.tsx index 69a7983..08ad784 100644 --- a/app/auth/login.tsx +++ b/app/auth/login.tsx @@ -108,14 +108,6 @@ export default function LoginScreen() { Hệ thống giám sát tàu cá - {/* Owner Logo */} - - - Đăng nhập để tiếp tục @@ -263,13 +255,6 @@ const styles = StyleSheet.create({ color: "#007AFF", fontWeight: "600", }, - ownerContainer: { - alignItems: "center", - }, - ownerLogo: { - width: 150, - height: 50, - }, copyrightContainer: { marginTop: 20, alignItems: "center", diff --git a/components/tripInfo/CrewListTable.tsx b/components/tripInfo/CrewListTable.tsx index 762fd96..0265420 100644 --- a/components/tripInfo/CrewListTable.tsx +++ b/components/tripInfo/CrewListTable.tsx @@ -1,6 +1,7 @@ import { IconSymbol } from "@/components/ui/icon-symbol"; import React, { useRef, useState } from "react"; import { Animated, Text, TouchableOpacity, View } from "react-native"; +import CrewDetailModal from "./modal/CrewDetailModal"; import styles from "./style/CrewListTable.styles"; // --------------------------- @@ -11,6 +12,14 @@ interface CrewMember { maDinhDanh: string; ten: string; chucVu: string; + ngaySinh?: string; + cccd?: string; + soDienThoai?: string; + diaChi?: string; + ngayVaoLam?: string; + trinhDoChuyenMon?: string; + bangCap?: string; + tinhTrang?: string; } // --------------------------- @@ -22,21 +31,65 @@ const data: CrewMember[] = [ maDinhDanh: "ChuTau", ten: "Nguyễn Nhật Minh", chucVu: "Chủ tàu", + ngaySinh: "08/06/2006", + cccd: "079085012345", + soDienThoai: "0912345678", + diaChi: "Hà Nội", + ngayVaoLam: "", + trinhDoChuyenMon: "Thuyền trưởng hạng I", + bangCap: "Bằng thuyền trưởng xa bờ", + tinhTrang: "Đang làm việc", }, { id: "1", maDinhDanh: "TV001", ten: "Nguyễn Văn A", chucVu: "Thuyền trưởng", + ngaySinh: "20/05/1988", + cccd: "079088011111", + soDienThoai: "0901234567", + diaChi: "456 Đường Cảng, Phường Thanh Khê, Đà Nẵng", + ngayVaoLam: "15/06/2015", + trinhDoChuyenMon: "Thuyền trưởng hạng II", + bangCap: "Bằng thuyền trưởng ven bờ", + tinhTrang: "Đang làm việc", + }, + { + id: "2", + maDinhDanh: "TV002", + ten: "Trần Văn B", + chucVu: "Máy trưởng", + ngaySinh: "10/08/1990", + cccd: "079090022222", + soDienThoai: "0987654321", + diaChi: "789 Đường Nguyễn Văn Linh, Quận Sơn Trà, Đà Nẵng", + ngayVaoLam: "20/03/2016", + trinhDoChuyenMon: "Máy trưởng hạng III", + bangCap: "Bằng máy trưởng ven bờ", + tinhTrang: "Đang làm việc", + }, + { + id: "3", + maDinhDanh: "TV003", + ten: "Lê Văn C", + chucVu: "Thủy thủ", + ngaySinh: "25/12/1995", + cccd: "079095033333", + soDienThoai: "0976543210", + diaChi: "321 Đường Hoàng Sa, Quận Ngũ Hành Sơn, Đà Nẵng", + ngayVaoLam: "10/07/2018", + trinhDoChuyenMon: "Thủy thủ hạng I", + bangCap: "Chứng chỉ thủy thủ", + tinhTrang: "Đang làm việc", }, - { id: "2", maDinhDanh: "TV002", ten: "Trần Văn B", chucVu: "Máy trưởng" }, - { id: "3", maDinhDanh: "TV003", ten: "Lê Văn C", chucVu: "Thủy thủ" }, ]; const CrewListTable: React.FC = () => { const [collapsed, setCollapsed] = useState(true); const [contentHeight, setContentHeight] = useState(0); const animatedHeight = useRef(new Animated.Value(0)).current; + const [modalVisible, setModalVisible] = useState(false); + const [selectedCrew, setSelectedCrew] = useState(null); const tongThanhVien = data.length; const handleToggle = () => { @@ -50,7 +103,16 @@ const CrewListTable: React.FC = () => { }; const handleCrewPress = (crewId: string) => { - console.log("Crew ID:", crewId); + const crew = data.find((item) => item.id === crewId); + if (crew) { + setSelectedCrew(crew); + setModalVisible(true); + } + }; + + const handleCloseModal = () => { + setModalVisible(false); + setSelectedCrew(null); }; return ( @@ -157,6 +219,13 @@ const CrewListTable: React.FC = () => { + + {/* Modal chi tiết thuyền viên */} + ); }; diff --git a/components/tripInfo/TripCostTable.tsx b/components/tripInfo/TripCostTable.tsx index 76f3caf..05e9a90 100644 --- a/components/tripInfo/TripCostTable.tsx +++ b/components/tripInfo/TripCostTable.tsx @@ -1,6 +1,7 @@ import { IconSymbol } from "@/components/ui/icon-symbol"; import React, { useRef, useState } from "react"; import { Animated, Text, TouchableOpacity, View } from "react-native"; +import TripCostDetailModal from "./modal/TripCostDetailModal"; import styles from "./style/TripCostTable.styles"; // --------------------------- @@ -60,6 +61,7 @@ const data: CostItem[] = [ const TripCostTable: React.FC = () => { const [collapsed, setCollapsed] = useState(true); const [contentHeight, setContentHeight] = useState(0); + const [modalVisible, setModalVisible] = useState(false); const animatedHeight = useRef(new Animated.Value(0)).current; const tongCong = data.reduce((sum, item) => sum + item.tongChiPhi, 0); @@ -74,7 +76,11 @@ const TripCostTable: React.FC = () => { }; const handleViewDetail = () => { - console.log("View trip cost details"); + setModalVisible(true); + }; + + const handleCloseModal = () => { + setModalVisible(false); }; return ( @@ -191,6 +197,13 @@ const TripCostTable: React.FC = () => { Xem chi tiết + + {/* Modal */} + ); }; diff --git a/components/tripInfo/modal/CrewDetailModal.tsx b/components/tripInfo/modal/CrewDetailModal.tsx new file mode 100644 index 0000000..13b776b --- /dev/null +++ b/components/tripInfo/modal/CrewDetailModal.tsx @@ -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 = ({ + 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 ( + + + {/* Header */} + + Thông tin thuyền viên + + + + + + + + {/* Content */} + + + {infoItems.map((item, index) => ( + + {item.label} + {item.value} + + ))} + + + + + ); +}; + +export default CrewDetailModal; diff --git a/components/tripInfo/modal/TripCostDetailModal.tsx b/components/tripInfo/modal/TripCostDetailModal.tsx new file mode 100644 index 0000000..d1cfe7f --- /dev/null +++ b/components/tripInfo/modal/TripCostDetailModal.tsx @@ -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 = ({ + visible, + onClose, + data, +}) => { + const [isEditing, setIsEditing] = useState(false); + const [editableData, setEditableData] = useState(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 ( + + + + {/* Header */} + + Chi tiết chi phí chuyến đi + + {isEditing ? ( + <> + + Hủy + + + Lưu + + + ) : ( + + + + + + )} + + + + + + + + + {/* Content */} + + {editableData.map((item) => ( + + {/* Loại */} + + Loại chi phí + updateItem(item.id, "loai", value)} + editable={isEditing} + placeholder="Nhập loại chi phí" + /> + + + {/* Số lượng & Đơn vị */} + + + Số lượng + + updateItem(item.id, "soLuong", value) + } + editable={isEditing} + keyboardType="numeric" + placeholder="0" + /> + + + Đơn vị + + updateItem(item.id, "donVi", value) + } + editable={isEditing} + placeholder="kg, lít..." + /> + + + + {/* Chi phí/đơn vị */} + + Chi phí/đơn vị (VNĐ) + + updateItem(item.id, "chiPhi", value) + } + editable={isEditing} + keyboardType="numeric" + placeholder="0" + /> + + + {/* Tổng chi phí */} + + Tổng chi phí + + + {item.tongChiPhi.toLocaleString()} VNĐ + + + + + ))} + + {/* Footer Total */} + + Tổng cộng + + {tongCong.toLocaleString()} VNĐ + + + + + + + ); +}; + +export default TripCostDetailModal; diff --git a/components/tripInfo/modal/style/CrewDetailModal.styles.ts b/components/tripInfo/modal/style/CrewDetailModal.styles.ts new file mode 100644 index 0000000..cf8ea30 --- /dev/null +++ b/components/tripInfo/modal/style/CrewDetailModal.styles.ts @@ -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; diff --git a/components/tripInfo/modal/style/TripCostDetailModal.styles.ts b/components/tripInfo/modal/style/TripCostDetailModal.styles.ts new file mode 100644 index 0000000..3f2f585 --- /dev/null +++ b/components/tripInfo/modal/style/TripCostDetailModal.styles.ts @@ -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; diff --git a/components/ui/icon-symbol.tsx b/components/ui/icon-symbol.tsx index 7937b72..c014d69 100644 --- a/components/ui/icon-symbol.tsx +++ b/components/ui/icon-symbol.tsx @@ -28,6 +28,8 @@ const MAPPING = { "exclamationmark.triangle.fill": "warning", "book.closed.fill": "book", "dot.radiowaves.left.and.right": "sensors", + xmark: "close", + pencil: "edit", } as IconMapping; /** diff --git a/package-lock.json b/package-lock.json index 301258b..efb9344 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-maps": "^1.20.1", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "5.4.0", @@ -10817,7 +10818,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -10829,7 +10829,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/proxy-from-env": { @@ -11345,6 +11344,15 @@ "react-native": "*" } }, + "node_modules/react-native-iphone-x-helper": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz", + "integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.42.0" + } + }, "node_modules/react-native-is-edge-to-edge": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", @@ -11355,6 +11363,19 @@ "react-native": "*" } }, + "node_modules/react-native-keyboard-aware-scroll-view": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/react-native-keyboard-aware-scroll-view/-/react-native-keyboard-aware-scroll-view-0.9.5.tgz", + "integrity": "sha512-XwfRn+T/qBH9WjTWIBiJD2hPWg0yJvtaEw6RtPCa5/PYHabzBaWxYBOl0usXN/368BL1XktnZPh8C2lmTpOREA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.6.2", + "react-native-iphone-x-helper": "^1.0.3" + }, + "peerDependencies": { + "react-native": ">=0.48.4" + } + }, "node_modules/react-native-maps": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.20.1.tgz", diff --git a/package.json b/package.json index 5d341c4..50e5934 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-maps": "^1.20.1", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "5.4.0",