From 47e9bac0f9e8e41d38df0bc8bd62454cd9a714ca Mon Sep 17 00:00:00 2001 From: MinhNN Date: Wed, 3 Dec 2025 00:10:11 +0700 Subject: [PATCH] update tab diary --- app/(tabs)/diary.tsx | 205 +++++++++++++++++++-- components/diary/DateRangePicker.tsx | 243 ++++++++++++++++++++++++ components/diary/FilterButton.tsx | 53 ++++++ components/diary/FilterModal.tsx | 264 +++++++++++++++++++++++++++ components/diary/SearchBar.tsx | 68 +++++++ components/diary/StatusDropdown.tsx | 183 +++++++++++++++++++ components/diary/TripCard.tsx | 181 ++++++++++++++++++ components/diary/mockData.ts | 70 +++++++ components/diary/types.ts | 44 +++++ package-lock.json | 24 +++ package.json | 1 + 11 files changed, 1323 insertions(+), 13 deletions(-) create mode 100644 components/diary/DateRangePicker.tsx create mode 100644 components/diary/FilterButton.tsx create mode 100644 components/diary/FilterModal.tsx create mode 100644 components/diary/SearchBar.tsx create mode 100644 components/diary/StatusDropdown.tsx create mode 100644 components/diary/TripCard.tsx create mode 100644 components/diary/mockData.ts create mode 100644 components/diary/types.ts diff --git a/app/(tabs)/diary.tsx b/app/(tabs)/diary.tsx index 471d33e..0354918 100644 --- a/app/(tabs)/diary.tsx +++ b/app/(tabs)/diary.tsx @@ -1,31 +1,210 @@ -import { Platform, ScrollView, StyleSheet, Text, View } from "react-native"; +import { useState } from "react"; +import { Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; +import { Ionicons } from "@expo/vector-icons"; +import SearchBar from "@/components/diary/SearchBar"; +import FilterButton from "@/components/diary/FilterButton"; +import TripCard from "@/components/diary/TripCard"; +import FilterModal, { FilterValues } from "@/components/diary/FilterModal"; +import { MOCK_TRIPS } from "@/components/diary/mockData"; export default function diary() { + const [searchText, setSearchText] = useState(""); + const [showFilterModal, setShowFilterModal] = useState(false); + const [filters, setFilters] = useState({ + status: null, + startDate: null, + endDate: null, + }); + + // Filter trips based on search text and filters + const filteredTrips = MOCK_TRIPS.filter((trip) => { + // Search filter + if (searchText) { + const searchLower = searchText.toLowerCase(); + const matchesSearch = + trip.title.toLowerCase().includes(searchLower) || + trip.code.toLowerCase().includes(searchLower) || + trip.vessel.toLowerCase().includes(searchLower) || + trip.vesselCode.toLowerCase().includes(searchLower); + + if (!matchesSearch) return false; + } + + // Status filter + if (filters.status && trip.status !== filters.status) { + return false; + } + + // Date range filter + if (filters.startDate || filters.endDate) { + const tripDate = new Date(trip.departureDate); + + if (filters.startDate && tripDate < filters.startDate) { + return false; + } + + if (filters.endDate) { + const endOfDay = new Date(filters.endDate); + endOfDay.setHours(23, 59, 59, 999); + if (tripDate > endOfDay) { + return false; + } + } + } + + return true; + }); + + const handleSearch = (text: string) => { + setSearchText(text); + }; + + const handleFilter = () => { + setShowFilterModal(true); + }; + + const handleApplyFilters = (newFilters: FilterValues) => { + setFilters(newFilters); + }; + + const handleTripPress = (tripId: string) => { + // TODO: Navigate to trip detail + console.log("Trip pressed:", tripId); + }; + return ( - - - - Nhật ký chuyến đi + + + {/* Header */} + Nhật ký chuyến đi + + {/* Search Bar */} + + + {/* Filter Button */} + + + {/* Trip Count & Add Button */} + + + Danh sách chuyến đi ({filteredTrips.length}) + + console.log("Add trip")} + activeOpacity={0.7} + > + + Thêm chuyến đi + - + + {/* Trip List */} + + {filteredTrips.map((trip) => ( + handleTripPress(trip.id)} + /> + ))} + + {filteredTrips.length === 0 && ( + + + Không tìm thấy chuyến đi phù hợp + + + )} + + + + {/* Filter Modal */} + setShowFilterModal(false)} + onApply={handleApplyFilters} + /> ); } const styles = StyleSheet.create({ - scrollContent: { - flexGrow: 1, + safeArea: { + flex: 1, + backgroundColor: "#F9FAFB", }, container: { - alignItems: "center", - padding: 15, + flex: 1, + padding: 16, }, titleText: { - fontSize: 32, + fontSize: 28, fontWeight: "700", - lineHeight: 40, - marginBottom: 30, + lineHeight: 36, + marginBottom: 20, + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + headerRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginTop: 20, + marginBottom: 12, + }, + countText: { + fontSize: 16, + fontWeight: "600", + color: "#374151", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + addButton: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "#3B82F6", + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 8, + gap: 6, + }, + addButtonText: { + fontSize: 14, + fontWeight: "600", + color: "#FFFFFF", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + scrollView: { + flex: 1, + }, + scrollContent: { + paddingBottom: 20, + }, + emptyState: { + alignItems: "center", + justifyContent: "center", + paddingVertical: 60, + }, + emptyText: { + fontSize: 16, + color: "#9CA3AF", fontFamily: Platform.select({ ios: "System", android: "Roboto", diff --git a/components/diary/DateRangePicker.tsx b/components/diary/DateRangePicker.tsx new file mode 100644 index 0000000..8337c14 --- /dev/null +++ b/components/diary/DateRangePicker.tsx @@ -0,0 +1,243 @@ +import React, { useState } from "react"; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Platform, + Modal, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import DateTimePicker from "@react-native-community/datetimepicker"; + +interface DateRangePickerProps { + startDate: Date | null; + endDate: Date | null; + onStartDateChange: (date: Date | null) => void; + onEndDateChange: (date: Date | null) => void; +} + +export default function DateRangePicker({ + startDate, + endDate, + onStartDateChange, + onEndDateChange, +}: DateRangePickerProps) { + const [showStartPicker, setShowStartPicker] = useState(false); + const [showEndPicker, setShowEndPicker] = useState(false); + + const formatDate = (date: Date | null) => { + if (!date) return ""; + const day = date.getDate().toString().padStart(2, "0"); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const year = date.getFullYear(); + return `${day}/${month}/${year}`; + }; + + const handleStartDateChange = (event: any, selectedDate?: Date) => { + setShowStartPicker(Platform.OS === "ios"); + if (selectedDate) { + onStartDateChange(selectedDate); + } + }; + + const handleEndDateChange = (event: any, selectedDate?: Date) => { + setShowEndPicker(Platform.OS === "ios"); + if (selectedDate) { + onEndDateChange(selectedDate); + } + }; + + return ( + + Ngày đi + + {/* Start Date */} + setShowStartPicker(true)} + activeOpacity={0.7} + > + + {startDate ? formatDate(startDate) : "Ngày bắt đầu"} + + + + + + {/* End Date */} + setShowEndPicker(true)} + activeOpacity={0.7} + > + + {endDate ? formatDate(endDate) : "Ngày kết thúc"} + + + + setShowStartPicker(true)} + > + + + + + {/* Start Date Picker */} + {showStartPicker && ( + + + + + setShowStartPicker(false)}> + Hủy + + Chọn ngày bắt đầu + setShowStartPicker(false)}> + Xong + + + + + + + )} + + {/* End Date Picker */} + {showEndPicker && ( + + + + + setShowEndPicker(false)}> + Hủy + + Chọn ngày kết thúc + setShowEndPicker(false)}> + Xong + + + + + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + marginBottom: 20, + }, + label: { + fontSize: 16, + fontWeight: "600", + color: "#111827", + marginBottom: 8, + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + dateRangeContainer: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + dateInput: { + flex: 1, + backgroundColor: "#FFFFFF", + borderWidth: 1, + borderColor: "#D1D5DB", + borderRadius: 8, + paddingHorizontal: 16, + paddingVertical: 12, + }, + dateText: { + fontSize: 16, + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + placeholder: { + color: "#9CA3AF", + }, + arrow: { + marginHorizontal: 4, + }, + calendarButton: { + padding: 8, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", + justifyContent: "flex-end", + }, + pickerContainer: { + backgroundColor: "#FFFFFF", + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + paddingBottom: 20, + }, + pickerHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: 20, + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: "#F3F4F6", + }, + pickerTitle: { + fontSize: 16, + fontWeight: "600", + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + cancelButton: { + fontSize: 16, + color: "#6B7280", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + doneButton: { + fontSize: 16, + fontWeight: "600", + color: "#3B82F6", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, +}); diff --git a/components/diary/FilterButton.tsx b/components/diary/FilterButton.tsx new file mode 100644 index 0000000..cbef831 --- /dev/null +++ b/components/diary/FilterButton.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { TouchableOpacity, Text, StyleSheet, Platform } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; + +interface FilterButtonProps { + onPress?: () => void; +} + +export default function FilterButton({ onPress }: FilterButtonProps) { + return ( + + + Bộ lọc + + ); +} + +const styles = StyleSheet.create({ + button: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#FFFFFF", + borderRadius: 12, + paddingHorizontal: 20, + paddingVertical: 12, + borderWidth: 1, + borderColor: "#E5E7EB", + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 1, + }, + shadowOpacity: 0.05, + shadowRadius: 2, + elevation: 1, + }, + text: { + fontSize: 16, + fontWeight: "500", + color: "#374151", + marginLeft: 8, + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, +}); diff --git a/components/diary/FilterModal.tsx b/components/diary/FilterModal.tsx new file mode 100644 index 0000000..f75cd5d --- /dev/null +++ b/components/diary/FilterModal.tsx @@ -0,0 +1,264 @@ +import React, { useState } from "react"; +import { + View, + Text, + Modal, + TouchableOpacity, + StyleSheet, + Platform, + ScrollView, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import StatusDropdown from "./StatusDropdown"; +import DateRangePicker from "./DateRangePicker"; +import { TripStatus } from "./types"; + +interface FilterModalProps { + visible: boolean; + onClose: () => void; + onApply: (filters: FilterValues) => void; +} + +export interface FilterValues { + status: TripStatus | null; + startDate: Date | null; + endDate: Date | null; +} + +export default function FilterModal({ + visible, + onClose, + onApply, +}: FilterModalProps) { + const [status, setStatus] = useState(null); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + + const handleReset = () => { + setStatus(null); + setStartDate(null); + setEndDate(null); + }; + + const handleApply = () => { + onApply({ status, startDate, endDate }); + onClose(); + }; + + const hasFilters = status !== null || startDate !== null || endDate !== null; + + return ( + + + e.stopPropagation()} + > + {/* Header */} + + + + + Bộ lọc + + + + {/* Content */} + + + + + {/* Filter Results Preview */} + {hasFilters && ( + + Bộ lọc đã chọn: + {status && ( + + + Trạng thái: {status} + + + )} + {startDate && ( + + + Từ: {startDate.toLocaleDateString("vi-VN")} + + + )} + {endDate && ( + + + Đến: {endDate.toLocaleDateString("vi-VN")} + + + )} + + )} + + + {/* Footer */} + + + Đặt lại + + + Áp dụng + + + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", + justifyContent: "flex-end", + }, + modalContainer: { + backgroundColor: "#FFFFFF", + borderTopLeftRadius: 24, + borderTopRightRadius: 24, + maxHeight: "80%", + shadowColor: "#000", + shadowOffset: { + width: 0, + height: -4, + }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 8, + }, + header: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: 20, + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: "#F3F4F6", + }, + closeButton: { + padding: 4, + }, + title: { + fontSize: 18, + fontWeight: "700", + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + placeholder: { + width: 32, + }, + content: { + padding: 20, + }, + previewContainer: { + marginTop: 20, + padding: 16, + backgroundColor: "#F9FAFB", + borderRadius: 12, + }, + previewTitle: { + fontSize: 14, + fontWeight: "600", + color: "#6B7280", + marginBottom: 12, + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + filterTag: { + backgroundColor: "#EFF6FF", + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 16, + marginBottom: 8, + alignSelf: "flex-start", + }, + filterTagText: { + fontSize: 14, + color: "#3B82F6", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + footer: { + flexDirection: "row", + gap: 12, + padding: 20, + borderTopWidth: 1, + borderTopColor: "#F3F4F6", + }, + resetButton: { + flex: 1, + backgroundColor: "#F3F4F6", + paddingVertical: 14, + borderRadius: 12, + alignItems: "center", + }, + resetButtonText: { + fontSize: 16, + fontWeight: "600", + color: "#6B7280", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + applyButton: { + flex: 1, + backgroundColor: "#3B82F6", + paddingVertical: 14, + borderRadius: 12, + alignItems: "center", + }, + applyButtonText: { + fontSize: 16, + fontWeight: "600", + color: "#FFFFFF", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, +}); diff --git a/components/diary/SearchBar.tsx b/components/diary/SearchBar.tsx new file mode 100644 index 0000000..08d9fa2 --- /dev/null +++ b/components/diary/SearchBar.tsx @@ -0,0 +1,68 @@ +import React, { useState } from "react"; +import { View, TextInput, StyleSheet, Platform, StyleProp, ViewStyle } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; + +interface SearchBarProps { + onSearch?: (text: string) => void; + style?: StyleProp; +} + +export default function SearchBar({ onSearch, style }: SearchBarProps) { + const [searchText, setSearchText] = useState(""); + + const handleChangeText = (text: string) => { + setSearchText(text); + onSearch?.(text); + }; + + return ( + + + + {searchText.length > 0 && ( + handleChangeText("")} + /> + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "#F9FAFB", + borderRadius: 12, + paddingHorizontal: 16, + paddingVertical: 12, + borderWidth: 1, + borderColor: "#E5E7EB", + }, + icon: { + marginRight: 8, + }, + input: { + flex: 1, + fontSize: 16, + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + clearIcon: { + marginLeft: 8, + }, +}); diff --git a/components/diary/StatusDropdown.tsx b/components/diary/StatusDropdown.tsx new file mode 100644 index 0000000..a221324 --- /dev/null +++ b/components/diary/StatusDropdown.tsx @@ -0,0 +1,183 @@ +import React, { useState } from "react"; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Modal, + Platform, + ScrollView, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { TripStatus, TRIP_STATUS_CONFIG } from "./types"; + +interface StatusDropdownProps { + value: TripStatus | null; + onChange: (status: TripStatus | null) => void; +} + +const STATUS_OPTIONS: Array<{ value: TripStatus | null; label: string }> = [ + { value: null, label: "Vui lòng chọn" }, + { value: "completed", label: "Hoàn thành" }, + { value: "in-progress", label: "Đang hoạt động" }, + { value: "quality-check", label: "Đã khởi tạo" }, + { value: "cancelled", label: "Đã hủy" }, +]; + +export default function StatusDropdown({ + value, + onChange, +}: StatusDropdownProps) { + const [isOpen, setIsOpen] = useState(false); + + const selectedLabel = + STATUS_OPTIONS.find((opt) => opt.value === value)?.label || "Vui lòng chọn"; + + const handleSelect = (status: TripStatus | null) => { + onChange(status); + setIsOpen(false); + }; + + return ( + + Trạng thái + setIsOpen(true)} + activeOpacity={0.7} + > + + {selectedLabel} + + + + + setIsOpen(false)} + > + setIsOpen(false)} + > + + + {STATUS_OPTIONS.map((option, index) => ( + handleSelect(option.value)} + > + + {option.label} + + {value === option.value && ( + + )} + + ))} + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + marginBottom: 20, + }, + label: { + fontSize: 16, + fontWeight: "600", + color: "#111827", + marginBottom: 8, + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + selector: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: "#FFFFFF", + borderWidth: 1, + borderColor: "#D1D5DB", + borderRadius: 8, + paddingHorizontal: 16, + paddingVertical: 12, + }, + selectorText: { + fontSize: 16, + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + placeholder: { + color: "#9CA3AF", + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", + justifyContent: "center", + alignItems: "center", + }, + modalContent: { + backgroundColor: "#FFFFFF", + borderRadius: 12, + width: "80%", + maxHeight: "60%", + overflow: "hidden", + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, + option: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: 20, + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: "#F3F4F6", + + }, + selectedOption: { + backgroundColor: "#EFF6FF", + }, + optionText: { + fontSize: 16, + color: "#111827", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + selectedOptionText: { + color: "#3B82F6", + fontWeight: "600", + }, +}); diff --git a/components/diary/TripCard.tsx b/components/diary/TripCard.tsx new file mode 100644 index 0000000..bcfc60b --- /dev/null +++ b/components/diary/TripCard.tsx @@ -0,0 +1,181 @@ +import React from "react"; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + Platform, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { Trip, TRIP_STATUS_CONFIG } from "./types"; + +interface TripCardProps { + trip: Trip; + onPress?: () => void; +} + +export default function TripCard({ trip, onPress }: TripCardProps) { + const statusConfig = TRIP_STATUS_CONFIG[trip.status]; + + return ( + + {/* Header */} + + + + + {trip.title} + {trip.code} + + + + + {statusConfig.label} + + + + + {/* Info Grid */} + + + Tàu + + {trip.vessel} ({trip.vesselCode}) + + + + + Khởi hành + {trip.departureDate} + + + + Trở về + {trip.returnDate || "-"} + + + + Thời gian + {trip.duration} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: "#FFFFFF", + borderRadius: 12, + padding: 16, + marginBottom: 12, + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.05, + shadowRadius: 8, + elevation: 2, + borderWidth: 1, + borderColor: "#F3F4F6", + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-start", + marginBottom: 16, + }, + headerLeft: { + flexDirection: "row", + alignItems: "center", + flex: 1, + }, + titleContainer: { + marginLeft: 12, + flex: 1, + }, + title: { + fontSize: 16, + fontWeight: "600", + color: "#111827", + marginBottom: 2, + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + code: { + fontSize: 14, + color: "#6B7280", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + badge: { + paddingHorizontal: 12, + paddingVertical: 4, + borderRadius: 12, + }, + badgeText: { + fontSize: 12, + fontWeight: "500", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + infoGrid: { + gap: 12, + }, + infoRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + label: { + fontSize: 14, + color: "#6B7280", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + value: { + fontSize: 14, + color: "#111827", + fontWeight: "500", + textAlign: "right", + fontFamily: Platform.select({ + ios: "System", + android: "Roboto", + default: "System", + }), + }, + duration: { + color: "#3B82F6", + }, +}); diff --git a/components/diary/mockData.ts b/components/diary/mockData.ts new file mode 100644 index 0000000..fee5a59 --- /dev/null +++ b/components/diary/mockData.ts @@ -0,0 +1,70 @@ +import { Trip } from "./types"; + +export const MOCK_TRIPS: Trip[] = [ + { + id: "T001", + title: "Chuyến đi Hoàng Sa", + code: "T001", + vessel: "Hải Âu 1", + vesselCode: "V001", + departureDate: "2025-11-20 06:00", + returnDate: "2025-11-27 18:30", + duration: "7 ngày 12 giờ", + status: "completed", + }, + { + id: "T002", + title: "Tuần tra vùng biển", + code: "T002", + vessel: "Bình Minh", + vesselCode: "V004", + departureDate: "2025-11-26 08:00", + returnDate: null, + duration: "2 ngày 6 giờ", + status: "in-progress", + }, + { + id: "T003", + title: "Đánh cá Trường Sa", + code: "T003", + vessel: "Ngọc Lan", + vesselCode: "V002", + departureDate: "2025-11-15 05:30", + returnDate: "2025-11-25 16:00", + duration: "10 ngày 10 giờ", + status: "completed", + }, + { + id: "T004", + title: "Vận chuyển hàng hóa", + code: "T004", + vessel: "Việt Thắng", + vesselCode: "V003", + departureDate: "2025-11-22 10:00", + returnDate: null, + duration: "-", + status: "cancelled", + }, + { + id: "T005", + title: "Khảo sát địa chất", + code: "T005", + vessel: "Thanh Bình", + vesselCode: "V005", + departureDate: "2025-11-18 07:00", + returnDate: "2025-11-23 14:00", + duration: "5 ngày 7 giờ", + status: "quality-check", + }, + { + id: "T006", + title: "Đánh cá ven bờ", + code: "T006", + vessel: "Hải Âu 1", + vesselCode: "V001", + departureDate: "2025-11-28 04:00", + returnDate: null, + duration: "6 giờ", + status: "in-progress", + }, +]; diff --git a/components/diary/types.ts b/components/diary/types.ts new file mode 100644 index 0000000..8f09b3b --- /dev/null +++ b/components/diary/types.ts @@ -0,0 +1,44 @@ +export type TripStatus = + | "completed" + | "in-progress" + | "cancelled" + | "quality-check"; + +export interface Trip { + id: string; + title: string; + code: string; + vessel: string; + vesselCode: string; + departureDate: string; + returnDate: string | null; + duration: string; + status: TripStatus; +} + +export const TRIP_STATUS_CONFIG = { + completed: { + label: "Hoàn thành", + bgColor: "#D1FAE5", + textColor: "#065F46", + icon: "checkmark-circle", + }, + "in-progress": { + label: "Đang diễn ra", + bgColor: "#DBEAFE", + textColor: "#1E40AF", + icon: "time", + }, + cancelled: { + label: "Đã hủy", + bgColor: "#FEE2E2", + textColor: "#991B1B", + icon: "close-circle", + }, + "quality-check": { + label: "Khảo sát địa chất", + bgColor: "#D1FAE5", + textColor: "#065F46", + icon: "checkmark-circle", + }, +} as const; diff --git a/package-lock.json b/package-lock.json index 5466063..1cc815b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@islacel/react-native-custom-switch": "^1.0.10", "@legendapp/motion": "^2.5.3", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/datetimepicker": "8.4.4", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", @@ -3974,6 +3975,29 @@ "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, + "node_modules/@react-native-community/datetimepicker": { + "version": "8.4.4", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.4.4.tgz", + "integrity": "sha512-bc4ZixEHxZC9/qf5gbdYvIJiLZ5CLmEsC3j+Yhe1D1KC/3QhaIfGDVdUcid0PdlSoGOSEq4VlB93AWyetEyBSQ==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "expo": ">=52.0.0", + "react": "*", + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", diff --git a/package.json b/package.json index 7a1a021..cf3d4bb 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@islacel/react-native-custom-switch": "^1.0.10", "@legendapp/motion": "^2.5.3", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/datetimepicker": "8.4.4", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8",