Files
sgw-owner-app/components/diary/TripFormModal/TripDurationPicker.tsx

576 lines
15 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
)}
{/* Unified Picker Modal */}
{activePicker && (
<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={Platform.OS === "ios" ? "spinner" : "default"}
onChange={
isStartPicker
? handleStartPickerChange
: handleEndPickerChange
}
maximumDate={
isStartPicker && !isTimePicker
? endDate || undefined
: undefined
}
minimumDate={
!isStartPicker && !isTimePicker
? startDate || undefined
: undefined
}
themeVariant={colorScheme}
textColor={colors.text}
locale={locale}
/>
</View>
</View>
</Modal>
)}
</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",
}),
},
});