387 lines
10 KiB
TypeScript
387 lines
10 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
Modal,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Platform,
|
|
ScrollView,
|
|
} from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useI18n } from "@/hooks/use-i18n";
|
|
import { useThemeContext } from "@/hooks/use-theme-context";
|
|
import FishingGearList from "@/components/diary/addTripModal/FishingGearList";
|
|
import MaterialCostList from "@/components/diary/addTripModal/MaterialCostList";
|
|
import TripNameInput from "@/components/diary/addTripModal/TripNameInput";
|
|
import TripDurationPicker from "@/components/diary/addTripModal/TripDurationPicker";
|
|
import PortSelector from "@/components/diary/addTripModal/PortSelector";
|
|
import BasicInfoInput from "@/components/diary/addTripModal/BasicInfoInput";
|
|
import ShipSelector from "./ShipSelector";
|
|
import AutoFillSection from "./AutoFillSection";
|
|
|
|
|
|
// Internal component interfaces
|
|
export interface FishingGear {
|
|
id: string;
|
|
name: string;
|
|
number: string; // Changed from quantity to number (string)
|
|
}
|
|
|
|
export interface TripCost {
|
|
id: string;
|
|
type: string;
|
|
amount: number;
|
|
unit: string;
|
|
cost_per_unit: number;
|
|
total_cost: number;
|
|
}
|
|
|
|
// API body interface
|
|
export interface TripAPIBody {
|
|
thing_id?: string; // Ship ID
|
|
name: string;
|
|
departure_time: string; // ISO string
|
|
departure_port_id: number;
|
|
arrival_time: string; // ISO string
|
|
arrival_port_id: number;
|
|
fishing_ground_codes: number[]; // Array of numbers
|
|
fishing_gears: Array<{
|
|
name: string;
|
|
number: string;
|
|
}>;
|
|
trip_cost: Array<{
|
|
type: string;
|
|
amount: number;
|
|
unit: string;
|
|
cost_per_unit: number;
|
|
total_cost: number;
|
|
}>;
|
|
}
|
|
|
|
interface AddTripModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
|
const { t } = useI18n();
|
|
const { colors } = useThemeContext();
|
|
|
|
// Form state
|
|
const [selectedShipId, setSelectedShipId] = useState<string>("");
|
|
const [tripName, setTripName] = useState("");
|
|
const [fishingGears, setFishingGears] = useState<FishingGear[]>([]);
|
|
const [tripCosts, setTripCosts] = useState<TripCost[]>([]);
|
|
const [startDate, setStartDate] = useState<Date | null>(null);
|
|
const [endDate, setEndDate] = useState<Date | null>(null);
|
|
const [departurePortId, setDeparturePortId] = useState<number>(1);
|
|
const [arrivalPortId, setArrivalPortId] = useState<number>(1);
|
|
const [fishingGroundCodes, setFishingGroundCodes] = useState<string>(""); // Input as string, convert to array
|
|
|
|
const handleCancel = () => {
|
|
// Reset form
|
|
setSelectedShipId("");
|
|
setTripName("");
|
|
setFishingGears([]);
|
|
setTripCosts([]);
|
|
setStartDate(null);
|
|
setEndDate(null);
|
|
setDeparturePortId(1);
|
|
setArrivalPortId(1);
|
|
setFishingGroundCodes("");
|
|
onClose();
|
|
};
|
|
|
|
// Handle auto-fill from last trip data
|
|
const handleAutoFill = (tripData: Model.Trip, selectedThingId: string) => {
|
|
// Fill ship ID (use the thingId from the selected ship for ShipSelector)
|
|
setSelectedShipId(selectedThingId);
|
|
|
|
// Fill trip name
|
|
if (tripData.name) {
|
|
setTripName(tripData.name);
|
|
}
|
|
|
|
// Fill fishing gears
|
|
if (tripData.fishing_gears && Array.isArray(tripData.fishing_gears)) {
|
|
const gears: FishingGear[] = tripData.fishing_gears.map((gear, index) => ({
|
|
id: `auto-${Date.now()}-${index}`,
|
|
name: gear.name || "",
|
|
number: gear.number?.toString() || "",
|
|
}));
|
|
setFishingGears(gears);
|
|
}
|
|
|
|
// Fill trip costs
|
|
if (tripData.trip_cost && Array.isArray(tripData.trip_cost)) {
|
|
const costs: TripCost[] = tripData.trip_cost.map((cost, index) => ({
|
|
id: `auto-${Date.now()}-${index}`,
|
|
type: cost.type || "",
|
|
amount: cost.amount || 0,
|
|
unit: cost.unit || "",
|
|
cost_per_unit: cost.cost_per_unit || 0,
|
|
total_cost: cost.total_cost || 0,
|
|
}));
|
|
setTripCosts(costs);
|
|
}
|
|
|
|
// Fill departure and arrival ports
|
|
if (tripData.departure_port_id) {
|
|
setDeparturePortId(tripData.departure_port_id);
|
|
}
|
|
if (tripData.arrival_port_id) {
|
|
setArrivalPortId(tripData.arrival_port_id);
|
|
}
|
|
|
|
// Fill fishing ground codes
|
|
if (tripData.fishing_ground_codes && Array.isArray(tripData.fishing_ground_codes)) {
|
|
setFishingGroundCodes(tripData.fishing_ground_codes.join(", "));
|
|
}
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
// Parse fishing ground codes from comma-separated string to array of numbers
|
|
const fishingGroundCodesArray = fishingGroundCodes
|
|
.split(",")
|
|
.map((code) => parseInt(code.trim()))
|
|
.filter((code) => !isNaN(code));
|
|
|
|
// Format API body
|
|
const apiBody: TripAPIBody = {
|
|
thing_id: selectedShipId || undefined,
|
|
name: tripName,
|
|
departure_time: startDate ? startDate.toISOString() : "",
|
|
departure_port_id: departurePortId,
|
|
arrival_time: endDate ? endDate.toISOString() : "",
|
|
arrival_port_id: arrivalPortId,
|
|
fishing_ground_codes: fishingGroundCodesArray,
|
|
fishing_gears: fishingGears.map((gear) => ({
|
|
name: gear.name,
|
|
number: gear.number,
|
|
})),
|
|
trip_cost: tripCosts.map((cost) => ({
|
|
type: cost.type,
|
|
amount: cost.amount,
|
|
unit: cost.unit,
|
|
cost_per_unit: cost.cost_per_unit,
|
|
total_cost: cost.total_cost,
|
|
})),
|
|
};
|
|
|
|
// Simulate API call - log the formatted data
|
|
console.log("=== Submitting Trip Data (API Format) ===");
|
|
console.log(JSON.stringify(apiBody, null, 2));
|
|
console.log("=== End Trip Data ===");
|
|
|
|
// Reset form and close modal
|
|
handleCancel();
|
|
};
|
|
|
|
const themedStyles = {
|
|
modalContainer: {
|
|
backgroundColor: colors.card,
|
|
},
|
|
header: {
|
|
borderBottomColor: colors.separator,
|
|
},
|
|
title: {
|
|
color: colors.text,
|
|
},
|
|
footer: {
|
|
borderTopColor: colors.separator,
|
|
},
|
|
cancelButton: {
|
|
backgroundColor: colors.backgroundSecondary,
|
|
},
|
|
cancelButtonText: {
|
|
color: colors.textSecondary,
|
|
},
|
|
submitButton: {
|
|
backgroundColor: colors.primary,
|
|
},
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
animationType="fade"
|
|
transparent
|
|
onRequestClose={onClose}
|
|
>
|
|
<View style={styles.overlay}>
|
|
<View style={[styles.modalContainer, themedStyles.modalContainer]}>
|
|
{/* Header */}
|
|
<View style={[styles.header, themedStyles.header]}>
|
|
<TouchableOpacity onPress={handleCancel} style={styles.closeButton}>
|
|
<Ionicons name="close" size={24} color={colors.text} />
|
|
</TouchableOpacity>
|
|
<Text style={[styles.title, themedStyles.title]}>
|
|
{t("diary.addTrip")}
|
|
</Text>
|
|
<View style={styles.placeholder} />
|
|
</View>
|
|
|
|
{/* Content */}
|
|
<ScrollView
|
|
style={styles.content}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* Auto Fill Section */}
|
|
<AutoFillSection onAutoFill={handleAutoFill} />
|
|
|
|
{/* Ship Selector */}
|
|
<ShipSelector
|
|
selectedShipId={selectedShipId}
|
|
onChange={setSelectedShipId}
|
|
/>
|
|
|
|
{/* Trip Name */}
|
|
<TripNameInput value={tripName} onChange={setTripName} />
|
|
|
|
{/* Fishing Gear List */}
|
|
<FishingGearList
|
|
items={fishingGears}
|
|
onChange={setFishingGears}
|
|
/>
|
|
|
|
{/* Trip Cost List */}
|
|
<MaterialCostList
|
|
items={tripCosts}
|
|
onChange={setTripCosts}
|
|
/>
|
|
|
|
{/* Trip Duration */}
|
|
<TripDurationPicker
|
|
startDate={startDate}
|
|
endDate={endDate}
|
|
onStartDateChange={setStartDate}
|
|
onEndDateChange={setEndDate}
|
|
/>
|
|
|
|
{/* Port Selector */}
|
|
<PortSelector
|
|
departurePortId={departurePortId}
|
|
arrivalPortId={arrivalPortId}
|
|
onDeparturePortChange={setDeparturePortId}
|
|
onArrivalPortChange={setArrivalPortId}
|
|
/>
|
|
|
|
{/* Fishing Ground Codes */}
|
|
<BasicInfoInput
|
|
fishingGroundCodes={fishingGroundCodes}
|
|
onChange={setFishingGroundCodes}
|
|
/>
|
|
</ScrollView>
|
|
|
|
{/* Footer */}
|
|
<View style={[styles.footer, themedStyles.footer]}>
|
|
<TouchableOpacity
|
|
style={[styles.cancelButton, themedStyles.cancelButton]}
|
|
onPress={handleCancel}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.cancelButtonText, themedStyles.cancelButtonText]}>
|
|
{t("common.cancel")}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[styles.submitButton, themedStyles.submitButton]}
|
|
onPress={handleSubmit}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={styles.submitButtonText}>
|
|
{t("diary.createTrip")}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: {
|
|
flex: 1,
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
justifyContent: "flex-end",
|
|
},
|
|
modalContainer: {
|
|
borderTopLeftRadius: 24,
|
|
borderTopRightRadius: 24,
|
|
maxHeight: "90%",
|
|
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,
|
|
},
|
|
closeButton: {
|
|
padding: 4,
|
|
},
|
|
title: {
|
|
fontSize: 18,
|
|
fontWeight: "700",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
placeholder: {
|
|
width: 32,
|
|
},
|
|
content: {
|
|
padding: 20,
|
|
},
|
|
footer: {
|
|
flexDirection: "row",
|
|
gap: 12,
|
|
padding: 20,
|
|
borderTopWidth: 1,
|
|
},
|
|
cancelButton: {
|
|
flex: 1,
|
|
paddingVertical: 14,
|
|
borderRadius: 12,
|
|
alignItems: "center",
|
|
},
|
|
cancelButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
submitButton: {
|
|
flex: 1,
|
|
paddingVertical: 14,
|
|
borderRadius: 12,
|
|
alignItems: "center",
|
|
},
|
|
submitButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
color: "#FFFFFF",
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
});
|