600 lines
16 KiB
TypeScript
600 lines
16 KiB
TypeScript
import React, { useState, useEffect } 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";
|
|
import { useI18n } from "@/hooks/use-i18n";
|
|
import { useThemeContext } from "@/hooks/use-theme-context";
|
|
|
|
interface TripDurationPickerProps {
|
|
startDate: Date | null;
|
|
endDate: Date | null;
|
|
onStartDateChange: (date: Date | null) => void;
|
|
onEndDateChange: (date: Date | null) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
type PickerType = "startDate" | "startTime" | "endDate" | "endTime" | null;
|
|
|
|
export default function TripDurationPicker({
|
|
startDate,
|
|
endDate,
|
|
onStartDateChange,
|
|
onEndDateChange,
|
|
disabled = false,
|
|
}: TripDurationPickerProps) {
|
|
const { t, locale } = useI18n();
|
|
const { colors, colorScheme } = useThemeContext();
|
|
|
|
// Single state for which picker is showing
|
|
const [activePicker, setActivePicker] = useState<PickerType>(null);
|
|
|
|
// Temp states to hold the picker value before confirming
|
|
const [tempStartDate, setTempStartDate] = useState<Date>(new Date());
|
|
const [tempEndDate, setTempEndDate] = useState<Date>(new Date());
|
|
|
|
// State hiển thị thời gian hiện tại
|
|
const [currentTime, setCurrentTime] = useState<Date>(new Date());
|
|
|
|
// Update current time every second
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
setCurrentTime(new Date());
|
|
}, 1000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
|
|
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 formatTime = (date: Date | null) => {
|
|
if (!date) return "";
|
|
const hours = date.getHours().toString().padStart(2, "0");
|
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
return `${hours}:${minutes}`;
|
|
};
|
|
|
|
// Open start date picker
|
|
const handleOpenStartDatePicker = () => {
|
|
const today = new Date();
|
|
const dateToUse = startDate || today;
|
|
if (!startDate) {
|
|
onStartDateChange(today);
|
|
}
|
|
setTempStartDate(dateToUse);
|
|
setActivePicker("startDate");
|
|
};
|
|
|
|
// Open start time picker
|
|
const handleOpenStartTimePicker = () => {
|
|
const today = new Date();
|
|
const dateToUse = startDate || today;
|
|
if (!startDate) {
|
|
onStartDateChange(today);
|
|
}
|
|
setTempStartDate(dateToUse);
|
|
setActivePicker("startTime");
|
|
};
|
|
|
|
// Open end date picker
|
|
const handleOpenEndDatePicker = () => {
|
|
const today = new Date();
|
|
const dateToUse = endDate || today;
|
|
if (!endDate) {
|
|
onEndDateChange(today);
|
|
}
|
|
setTempEndDate(dateToUse);
|
|
setActivePicker("endDate");
|
|
};
|
|
|
|
// Open end time picker
|
|
const handleOpenEndTimePicker = () => {
|
|
const today = new Date();
|
|
const dateToUse = endDate || today;
|
|
if (!endDate) {
|
|
onEndDateChange(today);
|
|
}
|
|
setTempEndDate(dateToUse);
|
|
setActivePicker("endTime");
|
|
};
|
|
|
|
const handleStartPickerChange = (event: any, selectedDate?: Date) => {
|
|
if (Platform.OS === "android") {
|
|
setActivePicker(null);
|
|
if (event.type === "set" && selectedDate) {
|
|
onStartDateChange(selectedDate);
|
|
}
|
|
} else if (selectedDate) {
|
|
setTempStartDate(selectedDate);
|
|
onStartDateChange(selectedDate);
|
|
}
|
|
};
|
|
|
|
const handleEndPickerChange = (event: any, selectedDate?: Date) => {
|
|
if (Platform.OS === "android") {
|
|
setActivePicker(null);
|
|
if (event.type === "set" && selectedDate) {
|
|
onEndDateChange(selectedDate);
|
|
}
|
|
} else if (selectedDate) {
|
|
setTempEndDate(selectedDate);
|
|
onEndDateChange(selectedDate);
|
|
}
|
|
};
|
|
|
|
const handleConfirm = () => {
|
|
setActivePicker(null);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setActivePicker(null);
|
|
};
|
|
|
|
const getPickerTitle = () => {
|
|
switch (activePicker) {
|
|
case "startDate":
|
|
return t("diary.selectStartDate");
|
|
case "startTime":
|
|
return t("diary.selectStartTime") || "Chọn giờ khởi hành";
|
|
case "endDate":
|
|
return t("diary.selectEndDate");
|
|
case "endTime":
|
|
return t("diary.selectEndTime") || "Chọn giờ kết thúc";
|
|
default:
|
|
return "";
|
|
}
|
|
};
|
|
|
|
const themedStyles = {
|
|
label: { color: colors.text },
|
|
dateInput: {
|
|
backgroundColor: colors.card,
|
|
borderColor: colors.border,
|
|
},
|
|
dateText: { color: colors.text },
|
|
placeholder: { color: colors.textSecondary },
|
|
pickerContainer: { backgroundColor: colors.card },
|
|
pickerHeader: { borderBottomColor: colors.border },
|
|
pickerTitle: { color: colors.text },
|
|
cancelButton: { color: colors.textSecondary },
|
|
sectionCard: {
|
|
backgroundColor: colors.backgroundSecondary || colors.card,
|
|
borderColor: colors.border,
|
|
},
|
|
sectionTitle: { color: colors.text },
|
|
};
|
|
|
|
const renderDateTimeSection = (
|
|
type: "start" | "end",
|
|
date: Date | null,
|
|
onOpenDate: () => void,
|
|
onOpenTime: () => void
|
|
) => {
|
|
const isStart = type === "start";
|
|
const icon = isStart ? "boat-outline" : "flag-outline";
|
|
const title = isStart ? t("diary.startDate") : t("diary.endDate");
|
|
const dateLabel = t("diary.date") || "Ngày";
|
|
const timeLabel = t("diary.time") || "Giờ";
|
|
|
|
return (
|
|
<View style={[styles.sectionCard, themedStyles.sectionCard]}>
|
|
{/* Section Header */}
|
|
<View style={styles.sectionHeader}>
|
|
<View
|
|
style={[
|
|
styles.iconContainer,
|
|
{ backgroundColor: isStart ? "#3B82F620" : "#10B98120" },
|
|
]}
|
|
>
|
|
<Ionicons
|
|
name={icon as any}
|
|
size={18}
|
|
color={isStart ? "#3B82F6" : "#10B981"}
|
|
/>
|
|
</View>
|
|
<Text style={[styles.sectionTitle, themedStyles.sectionTitle]}>
|
|
{title}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Date and Time Row */}
|
|
<View style={styles.dateTimeRow}>
|
|
{/* Date Picker */}
|
|
<TouchableOpacity
|
|
style={[styles.dateTimeInput, themedStyles.dateInput]}
|
|
onPress={disabled ? undefined : onOpenDate}
|
|
activeOpacity={disabled ? 1 : 0.7}
|
|
disabled={disabled}
|
|
>
|
|
<View style={styles.inputContent}>
|
|
<Ionicons
|
|
name="calendar-outline"
|
|
size={18}
|
|
color={date ? colors.primary : colors.textSecondary}
|
|
style={styles.inputIcon}
|
|
/>
|
|
<View style={styles.inputTextContainer}>
|
|
<Text style={[styles.inputLabel, themedStyles.placeholder]}>
|
|
{dateLabel}
|
|
</Text>
|
|
<Text
|
|
style={[
|
|
styles.inputValue,
|
|
themedStyles.dateText,
|
|
!date && themedStyles.placeholder,
|
|
]}
|
|
>
|
|
{date ? formatDate(date) : t("diary.selectDate")}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
{!disabled && (
|
|
<Ionicons
|
|
name="chevron-forward"
|
|
size={16}
|
|
color={colors.textSecondary}
|
|
/>
|
|
)}
|
|
</TouchableOpacity>
|
|
|
|
{/* Time Picker */}
|
|
<TouchableOpacity
|
|
style={[styles.dateTimeInput, themedStyles.dateInput]}
|
|
onPress={disabled ? undefined : onOpenTime}
|
|
activeOpacity={disabled ? 1 : 0.7}
|
|
disabled={disabled}
|
|
>
|
|
<View style={styles.inputContent}>
|
|
<Ionicons
|
|
name="time-outline"
|
|
size={18}
|
|
color={date ? colors.primary : colors.textSecondary}
|
|
style={styles.inputIcon}
|
|
/>
|
|
<View style={styles.inputTextContainer}>
|
|
<Text style={[styles.inputLabel, themedStyles.placeholder]}>
|
|
{timeLabel}
|
|
</Text>
|
|
<Text
|
|
style={[
|
|
styles.inputValue,
|
|
themedStyles.dateText,
|
|
!date && themedStyles.placeholder,
|
|
]}
|
|
>
|
|
{date ? formatTime(date) : "--:--"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
{!disabled && (
|
|
<Ionicons
|
|
name="chevron-forward"
|
|
size={16}
|
|
color={colors.textSecondary}
|
|
/>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const isStartPicker =
|
|
activePicker === "startDate" || activePicker === "startTime";
|
|
const isTimePicker =
|
|
activePicker === "startTime" || activePicker === "endTime";
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={[styles.label, themedStyles.label]}>
|
|
{t("diary.tripDuration")}
|
|
</Text>
|
|
|
|
{/* Hiển thị thời gian hiện tại */}
|
|
<View
|
|
style={[
|
|
styles.currentTimeContainer,
|
|
{ backgroundColor: colors.backgroundSecondary || colors.card },
|
|
]}
|
|
>
|
|
<Ionicons name="time-outline" size={18} color={colors.primary} />
|
|
<Text
|
|
style={[styles.currentTimeLabel, { color: colors.textSecondary }]}
|
|
>
|
|
{t("diary.currentTime") || "Thời gian hiện tại"}:
|
|
</Text>
|
|
<Text style={[styles.currentTimeValue, { color: colors.primary }]}>
|
|
{formatDate(currentTime)} {formatTime(currentTime)}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Start Section */}
|
|
{renderDateTimeSection(
|
|
"start",
|
|
startDate,
|
|
handleOpenStartDatePicker,
|
|
handleOpenStartTimePicker
|
|
)}
|
|
|
|
{/* Connection Line */}
|
|
<View style={styles.connectionContainer}>
|
|
<View
|
|
style={[styles.connectionLine, { backgroundColor: colors.border }]}
|
|
/>
|
|
<View
|
|
style={[styles.connectionDot, { backgroundColor: colors.primary }]}
|
|
/>
|
|
<View
|
|
style={[styles.connectionLine, { backgroundColor: colors.border }]}
|
|
/>
|
|
</View>
|
|
|
|
{/* End Section */}
|
|
{renderDateTimeSection(
|
|
"end",
|
|
endDate,
|
|
handleOpenEndDatePicker,
|
|
handleOpenEndTimePicker
|
|
)}
|
|
|
|
{/* iOS: Modal wrapper with spinner */}
|
|
{activePicker && Platform.OS === "ios" && (
|
|
<Modal transparent animationType="fade" visible={!!activePicker}>
|
|
<View style={styles.modalOverlay}>
|
|
<View
|
|
style={[styles.pickerContainer, themedStyles.pickerContainer]}
|
|
>
|
|
<View style={[styles.pickerHeader, themedStyles.pickerHeader]}>
|
|
<TouchableOpacity onPress={handleCancel}>
|
|
<Text
|
|
style={[styles.cancelButtonText, themedStyles.cancelButton]}
|
|
>
|
|
{t("common.cancel")}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<Text style={[styles.pickerTitle, themedStyles.pickerTitle]}>
|
|
{getPickerTitle()}
|
|
</Text>
|
|
<TouchableOpacity onPress={handleConfirm}>
|
|
<Text style={styles.doneButton}>{t("common.done")}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
<DateTimePicker
|
|
value={isStartPicker ? tempStartDate : tempEndDate}
|
|
mode={isTimePicker ? "time" : "date"}
|
|
display="spinner"
|
|
onChange={
|
|
isStartPicker
|
|
? handleStartPickerChange
|
|
: handleEndPickerChange
|
|
}
|
|
maximumDate={
|
|
isStartPicker && !isTimePicker
|
|
? endDate || undefined
|
|
: undefined
|
|
}
|
|
minimumDate={
|
|
!isTimePicker
|
|
? isStartPicker
|
|
? new Date()
|
|
: startDate || new Date()
|
|
: undefined
|
|
}
|
|
themeVariant={colorScheme}
|
|
textColor={colors.text}
|
|
locale={locale}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
)}
|
|
|
|
{/* Android: Native dialog (no Modal wrapper needed) */}
|
|
{activePicker && Platform.OS === "android" && (
|
|
<DateTimePicker
|
|
value={isStartPicker ? tempStartDate : tempEndDate}
|
|
mode={isTimePicker ? "time" : "date"}
|
|
display="default"
|
|
onChange={
|
|
isStartPicker ? handleStartPickerChange : handleEndPickerChange
|
|
}
|
|
maximumDate={
|
|
isStartPicker && !isTimePicker ? endDate || undefined : undefined
|
|
}
|
|
minimumDate={
|
|
!isTimePicker
|
|
? isStartPicker
|
|
? new Date()
|
|
: startDate || new Date()
|
|
: undefined
|
|
}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
marginBottom: 20,
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
marginBottom: 16,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
sectionCard: {
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
padding: 14,
|
|
},
|
|
sectionHeader: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
marginBottom: 12,
|
|
},
|
|
iconContainer: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 8,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginRight: 10,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 15,
|
|
fontWeight: "600",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
dateTimeRow: {
|
|
flexDirection: "row",
|
|
gap: 10,
|
|
},
|
|
dateTimeInput: {
|
|
flex: 1,
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
borderWidth: 1,
|
|
borderRadius: 10,
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 10,
|
|
},
|
|
inputContent: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
flex: 1,
|
|
},
|
|
inputIcon: {
|
|
marginRight: 10,
|
|
},
|
|
inputTextContainer: {
|
|
flex: 1,
|
|
},
|
|
inputLabel: {
|
|
fontSize: 11,
|
|
marginBottom: 2,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
inputValue: {
|
|
fontSize: 14,
|
|
fontWeight: "500",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
connectionContainer: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 20,
|
|
},
|
|
connectionLine: {
|
|
flex: 1,
|
|
height: 1,
|
|
},
|
|
connectionDot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
marginHorizontal: 8,
|
|
},
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
justifyContent: "flex-end",
|
|
},
|
|
pickerContainer: {
|
|
borderTopLeftRadius: 20,
|
|
borderTopRightRadius: 20,
|
|
paddingBottom: 20,
|
|
},
|
|
pickerHeader: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 16,
|
|
borderBottomWidth: 1,
|
|
},
|
|
pickerTitle: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
cancelButtonText: {
|
|
fontSize: 16,
|
|
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",
|
|
}),
|
|
},
|
|
currentTimeContainer: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
padding: 12,
|
|
borderRadius: 8,
|
|
marginBottom: 12,
|
|
gap: 8,
|
|
},
|
|
currentTimeLabel: {
|
|
fontSize: 13,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
currentTimeValue: {
|
|
fontSize: 14,
|
|
fontWeight: "600",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
});
|