Cập nhật API thêm trip (validate)
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
||||
StyleSheet,
|
||||
Platform,
|
||||
ScrollView,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useI18n } from "@/hooks/use-i18n";
|
||||
@@ -19,52 +21,24 @@ import PortSelector from "@/components/diary/addTripModal/PortSelector";
|
||||
import BasicInfoInput from "@/components/diary/addTripModal/BasicInfoInput";
|
||||
import ShipSelector from "./ShipSelector";
|
||||
import AutoFillSection from "./AutoFillSection";
|
||||
import { createTrip } from "@/controller/TripController";
|
||||
|
||||
|
||||
// Internal component interfaces
|
||||
export interface FishingGear {
|
||||
// Internal component interfaces - extend from Model with local id for state management
|
||||
export interface FishingGear extends Model.FishingGear {
|
||||
id: string;
|
||||
name: string;
|
||||
number: string; // Changed from quantity to number (string)
|
||||
}
|
||||
|
||||
export interface TripCost {
|
||||
export interface TripCost extends Model.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;
|
||||
onSuccess?: () => void; // Callback khi thêm chuyến đi thành công
|
||||
}
|
||||
|
||||
export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
export default function AddTripModal({ visible, onClose, onSuccess }: AddTripModalProps) {
|
||||
const { t } = useI18n();
|
||||
const { colors } = useThemeContext();
|
||||
|
||||
@@ -78,6 +52,7 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
const [departurePortId, setDeparturePortId] = useState<number>(1);
|
||||
const [arrivalPortId, setArrivalPortId] = useState<number>(1);
|
||||
const [fishingGroundCodes, setFishingGroundCodes] = useState<string>(""); // Input as string, convert to array
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleCancel = () => {
|
||||
// Reset form
|
||||
@@ -99,17 +74,19 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
setSelectedShipId(selectedThingId);
|
||||
|
||||
// Fill trip name
|
||||
if (tripData.name) {
|
||||
setTripName(tripData.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() || "",
|
||||
}));
|
||||
const gears: FishingGear[] = tripData.fishing_gears.map(
|
||||
(gear, index) => ({
|
||||
id: `auto-${Date.now()}-${index}`,
|
||||
name: gear.name || "",
|
||||
number: gear.number?.toString() || "",
|
||||
})
|
||||
);
|
||||
setFishingGears(gears);
|
||||
}
|
||||
|
||||
@@ -135,12 +112,33 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
}
|
||||
|
||||
// Fill fishing ground codes
|
||||
if (tripData.fishing_ground_codes && Array.isArray(tripData.fishing_ground_codes)) {
|
||||
if (
|
||||
tripData.fishing_ground_codes &&
|
||||
Array.isArray(tripData.fishing_ground_codes)
|
||||
) {
|
||||
setFishingGroundCodes(tripData.fishing_ground_codes.join(", "));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
const handleSubmit = async () => {
|
||||
// Validate thingId is required
|
||||
if (!selectedShipId) {
|
||||
Alert.alert(t("common.error"), t("diary.validation.shipRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate dates are required
|
||||
if (!startDate || !endDate) {
|
||||
Alert.alert(t("common.error"), t("diary.validation.datesRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate trip name is required
|
||||
if (!tripName.trim()) {
|
||||
Alert.alert(t("common.error"), t("diary.validation.tripNameRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse fishing ground codes from comma-separated string to array of numbers
|
||||
const fishingGroundCodesArray = fishingGroundCodes
|
||||
.split(",")
|
||||
@@ -148,8 +146,8 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
.filter((code) => !isNaN(code));
|
||||
|
||||
// Format API body
|
||||
const apiBody: TripAPIBody = {
|
||||
thing_id: selectedShipId || undefined,
|
||||
const apiBody: Model.TripAPIBody = {
|
||||
thing_id: selectedShipId,
|
||||
name: tripName,
|
||||
departure_time: startDate ? startDate.toISOString() : "",
|
||||
departure_port_id: departurePortId,
|
||||
@@ -169,13 +167,37 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
})),
|
||||
};
|
||||
|
||||
// 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();
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const response = await createTrip(selectedShipId, apiBody);
|
||||
|
||||
if (response.data) {
|
||||
// Show success alert
|
||||
Alert.alert(
|
||||
t("common.success"),
|
||||
t("diary.createTripSuccess")
|
||||
);
|
||||
|
||||
// Call onSuccess callback
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
// Reset form and close modal
|
||||
handleCancel();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error creating trip:", error);
|
||||
// Log detailed error information for debugging
|
||||
if (error.response) {
|
||||
console.error("Response status:", error.response.status);
|
||||
console.error("Response data:", JSON.stringify(error.response.data, null, 2));
|
||||
}
|
||||
console.log("Request body was:", JSON.stringify(apiBody, null, 2));
|
||||
Alert.alert(t("common.error"), t("diary.createTripError"));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const themedStyles = {
|
||||
@@ -240,16 +262,10 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
<TripNameInput value={tripName} onChange={setTripName} />
|
||||
|
||||
{/* Fishing Gear List */}
|
||||
<FishingGearList
|
||||
items={fishingGears}
|
||||
onChange={setFishingGears}
|
||||
/>
|
||||
<FishingGearList items={fishingGears} onChange={setFishingGears} />
|
||||
|
||||
{/* Trip Cost List */}
|
||||
<MaterialCostList
|
||||
items={tripCosts}
|
||||
onChange={setTripCosts}
|
||||
/>
|
||||
<MaterialCostList items={tripCosts} onChange={setTripCosts} />
|
||||
|
||||
{/* Trip Duration */}
|
||||
<TripDurationPicker
|
||||
@@ -281,18 +297,29 @@ export default function AddTripModal({ visible, onClose }: AddTripModalProps) {
|
||||
onPress={handleCancel}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[styles.cancelButtonText, themedStyles.cancelButtonText]}>
|
||||
<Text
|
||||
style={[styles.cancelButtonText, themedStyles.cancelButtonText]}
|
||||
>
|
||||
{t("common.cancel")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.submitButton, themedStyles.submitButton]}
|
||||
style={[
|
||||
styles.submitButton,
|
||||
themedStyles.submitButton,
|
||||
isSubmitting && styles.submitButtonDisabled
|
||||
]}
|
||||
onPress={handleSubmit}
|
||||
activeOpacity={0.7}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Text style={styles.submitButtonText}>
|
||||
{t("diary.createTrip")}
|
||||
</Text>
|
||||
{isSubmitting ? (
|
||||
<ActivityIndicator size="small" color="#FFFFFF" />
|
||||
) : (
|
||||
<Text style={styles.submitButtonText}>
|
||||
{t("diary.createTrip")}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -373,6 +400,9 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 12,
|
||||
alignItems: "center",
|
||||
},
|
||||
submitButtonDisabled: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
submitButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
|
||||
Reference in New Issue
Block a user