Files
SMATEC-FRONTEND/src/pages/Slave/SGW/Trip/components/CreateTrip.tsx

555 lines
19 KiB
TypeScript
Raw Blame History

import { apiQueryShips } from '@/services/slave/sgw/ShipController';
import {
apiCreateTrip,
apiQueryLastTrips,
} from '@/services/slave/sgw/TripController';
import { PlusOutlined } from '@ant-design/icons';
import type {
ProFormInstance,
SubmitterProps,
} from '@ant-design/pro-components';
import {
ModalForm,
ProForm,
ProFormDatePicker,
ProFormList,
ProFormSelect,
ProFormText,
StepsForm,
} from '@ant-design/pro-components';
import { FormattedMessage, useModel } from '@umijs/max';
import { Button, message, Table, Typography } from 'antd';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { useEffect, useRef, useState } from 'react';
dayjs.extend(utc);
interface CreateTripProps {
onSuccess?: (ok: boolean) => void;
ship_id?: string;
thing_id?: string;
}
interface TripFormValues {
name: string;
departure_time: any; // dayjs object or string
departure_port_id: number;
arrival_time?: any; // dayjs object or string
arrival_port_id?: number;
fishing_ground_codes?: number[];
fishing_gear?: SgwModel.FishingGear[];
trip_cost?: SgwModel.TripCost[];
ship_id?: string;
}
const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
const [visible, setVisible] = useState(false);
const [selectedThing, setSelectedThing] = useState<string | undefined>();
const [ships, setShips] = useState<SgwModel.ShipDetail[]>([]);
const [selectedShip, setSelectedShip] = useState<SgwModel.ShipDetail | null>(
null,
);
const [loading, setLoading] = useState(false);
const [lastTrip, setLastTrip] = useState<SgwModel.Trip | null>(null);
const [initialFormValues, setInitialFormValues] = useState<
Partial<TripFormValues>
>({});
const formRef = useRef<ProFormInstance<TripFormValues>>(null);
const { homeports, getHomeportsByProvinceCode } = useModel(
'slave.sgw.useHomePorts',
);
useEffect(() => {
getHomeportsByProvinceCode();
}, [getHomeportsByProvinceCode]);
useEffect(() => {
setSelectedThing(thing_id);
}, [thing_id]);
// Load danh sách tàu
const loadShips = async () => {
setLoading(true);
try {
const resp = await apiQueryShips({ offset: 0, limit: 100 });
setShips(resp.ships || []);
} catch (error) {
message.error('Không thể tải danh sách tàu');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (visible) {
loadShips();
}
}, [visible]);
// Auto-fill form when initialFormValues changes
useEffect(() => {
if (formRef.current && Object.keys(initialFormValues).length > 0) {
console.log('<27> InitialFormValues:', initialFormValues);
// Fill form after a short delay to ensure all steps are ready
setTimeout(() => {
if (formRef.current) {
console.log('<27> Filling form with values...');
formRef.current.setFieldsValue(initialFormValues);
}
}, 100);
}
}, [initialFormValues]);
// Load last trip when ship is selected
const loadLastTrip = async (thingId: string) => {
try {
setLoading(true);
const trip = await apiQueryLastTrips(thingId);
setLastTrip(trip);
// Prepare form values with last trip data
if (trip) {
console.log('📦 Raw trip data:', trip);
const formValues = {
name: trip.name,
departure_time: dayjs(trip.departure_time),
arrival_time: dayjs(trip.arrival_time),
departure_port_id: trip.departure_port_id,
arrival_port_id: trip.arrival_port_id,
fishing_ground_codes: trip.fishing_ground_codes || [],
fishing_gear: trip.fishing_gears || [],
trip_cost: trip.trip_cost || [],
};
console.log('📋 Prepared form values:', formValues);
setInitialFormValues(formValues);
message.success(
`Đã tải ${trip.fishing_gears?.length || 0} ngư cụ và ${
trip.trip_cost?.length || 0
} chi phí từ chuyến trước`,
);
}
} catch (error) {
console.error('Error loading last trip:', error);
setLastTrip(null);
} finally {
setLoading(false);
}
};
const handleSubmit = async (values: TripFormValues) => {
try {
if (!selectedShip) {
message.error('Vui lòng chọn tàu!');
return false;
}
if (!values.departure_port_id) {
message.error('Vui lòng chọn cảng khởi hành!');
return false;
}
if (!values.arrival_port_id) {
message.error('Vui lòng chọn cảng cập bến!');
return false;
}
const params: SgwModel.TripCreateParams = {
name: values.name,
departure_time: dayjs(values.departure_time as string | Date)
.utc()
.format(),
departure_port_id: values.departure_port_id,
arrival_time: dayjs(values.arrival_time as string | Date)
.utc()
.format(),
arrival_port_id: values.arrival_port_id,
fishing_ground_codes: Array.isArray(values.fishing_ground_codes)
? (values.fishing_ground_codes as (string | number)[])
.map((code: string | number) => Number(code))
.filter((n: number) => !isNaN(n))
: [],
fishing_gears: values.fishing_gear,
trip_cost: values.trip_cost || [],
};
const thingIdToUse = selectedShip.thing_id || selectedThing;
if (!thingIdToUse) {
message.error('Không tìm thấy thiết bị của tàu!');
return false;
}
const resp = await apiCreateTrip(thingIdToUse, params);
console.log('createTrip resp', resp);
if (resp && !resp?.trip_status) {
message.success('Tạo chuyến đi thành công!');
onSuccess?.(true);
setVisible(false);
setSelectedShip(null);
return true;
} else {
message.error(`Tạo chuyến đi thất bại!`);
return false;
}
} catch (err) {
console.error('Lỗi khi tạo chuyến đi:', err);
message.error('Tạo chuyến đi thất bại!');
return false;
}
};
return (
<>
<Button type="primary" key="primary" onClick={() => setVisible(true)}>
<PlusOutlined />{' '}
<FormattedMessage
id="pages.things.createTrip.text"
defaultMessage="Tạo chuyến đi"
/>
</Button>
<ModalForm
title="Tạo chuyến đi mới"
open={visible}
onOpenChange={(open) => {
setVisible(open);
if (!open) {
setSelectedShip(null);
}
}}
onFinish={handleSubmit}
modalProps={{
width: 900,
}}
layout="horizontal"
submitter={false}
>
<StepsForm<SgwModel.TripCreateParams>
stepsProps={{ size: 'small' }}
onFinish={(values) => handleSubmit(values as TripFormValues)}
submitter={{
render: (_: SubmitterProps, dom) => dom,
}}
formProps={{
layout: 'horizontal',
}}
formRef={formRef}
onCurrentChange={(current) => {
if (!lastTrip || !formRef.current) return;
if (current === 1) {
formRef.current.setFieldsValue({
name: lastTrip.name,
departure_time: dayjs(lastTrip.departure_time),
arrival_time: dayjs(lastTrip.arrival_time),
departure_port_id: lastTrip.departure_port_id,
arrival_port_id: lastTrip.arrival_port_id,
fishing_ground_codes: lastTrip.fishing_ground_codes || [],
});
}
if (current === 2) {
formRef.current.setFieldsValue({
fishing_gear: lastTrip.fishing_gears || [],
});
}
if (current === 3) {
formRef.current.setFieldsValue({
trip_cost: lastTrip.trip_cost || [],
});
}
}}
>
{/* Step 1: Chọn tàu */}
<StepsForm.StepForm
title="Chọn tàu"
preserve
onFinish={async () => {
if (!selectedShip) {
message.error('Vui lòng chọn một tàu!');
return false;
}
// Load last trip after selecting ship
const thingIdToUse = selectedShip.thing_id || selectedThing;
if (thingIdToUse) {
await loadLastTrip(thingIdToUse);
}
return true;
}}
>
<Typography.Title level={5}>Danh sách tàu</Typography.Title>
<Table
loading={loading}
dataSource={ships}
rowKey="id"
pagination={{ pageSize: 5 }}
rowSelection={{
type: 'radio',
selectedRowKeys: selectedShip ? [selectedShip.id!] : [],
onChange: (
_: React.Key[],
selectedRows: SgwModel.ShipDetail[],
) => {
setSelectedShip(selectedRows[0] || null);
},
}}
columns={[
{
title: 'Số đăng ký',
dataIndex: 'reg_number',
key: 'reg_number',
},
{ title: 'Tên tàu', dataIndex: 'name', key: 'name' },
{ title: 'Loại tàu', dataIndex: 'ship_type', key: 'ship_type' },
{ title: 'Cảng nhà', dataIndex: 'home_port', key: 'home_port' },
]}
/>
</StepsForm.StepForm>
{/* Step 2: Thông tin chuyến đi */}
<StepsForm.StepForm
title="Thông tin chuyến đi"
name="trip_info"
preserve
>
<ProFormText
name="name"
label="Tên chuyến đi"
rules={[
{ required: true, message: 'Vui lòng nhập tên chuyến đi' },
]}
/>
<ProForm.Group title="Thời gian chuyến đi">
<ProFormDatePicker
name="departure_time"
showTime
label="Thời gian bắt đầu"
width="md"
rules={[
{
required: true,
message: 'Vui lòng chọn thời gian bắt đầu',
},
{
validator: (_: unknown, value: unknown) => {
if (!value) return Promise.resolve();
const todayDate = dayjs().startOf('day');
const selectedDate = dayjs(value as string).startOf(
'day',
);
return selectedDate.isBefore(todayDate)
? Promise.reject(
new Error('Ngày bắt đầu không được trước hôm nay'),
)
: Promise.resolve();
},
},
]}
/>
<ProFormDatePicker
name="arrival_time"
showTime
label="Thời gian kết thúc"
width="md"
rules={[
{
required: true,
message: 'Vui lòng chọn thời gian kết thúc',
},
]}
/>
</ProForm.Group>
<ProForm.Group title="Cảng đi / Cảng đến">
<ProFormSelect
name="departure_port_id"
label="Cảng khởi hành"
width="md"
placeholder="Chọn cảng khởi hành"
options={
Array.isArray(homeports)
? homeports.map((p) => ({ label: p.name, value: p.id }))
: []
}
rules={[
{ required: true, message: 'Vui lòng chọn cảng khởi hành' },
]}
showSearch
/>
<ProFormSelect
name="arrival_port_id"
label="Cảng cập bến"
width="md"
placeholder="Chọn cảng cập bến"
options={
Array.isArray(homeports)
? homeports.map((p) => ({ label: p.name, value: p.id }))
: []
}
rules={[
{ required: true, message: 'Vui lòng chọn cảng cập bến' },
]}
showSearch
/>
</ProForm.Group>
<ProFormSelect
name="fishing_ground_codes"
label="Ô khai thác"
fieldProps={{
mode: 'tags',
style: { width: '100%' },
placeholder: 'Nhập các ô ngư trường, nhấn Enter để thêm',
}}
rules={[
{
required: true,
message: 'Vui lòng nhập ít nhất một ô khai thác!',
},
]}
options={[]}
/>
</StepsForm.StepForm>
{/* Step 3: Danh sách ngư cụ */}
<StepsForm.StepForm
title="Danh sách ngư cụ"
name="fishing_gear_step"
preserve
>
<ProForm.Group title="Danh sách ngư cụ">
<ProFormList
name="fishing_gear"
creatorButtonProps={{ creatorButtonText: 'Thêm ngư cụ' }}
copyIconProps={{ tooltipText: 'Sao chép' }}
deleteIconProps={{ tooltipText: 'Xóa' }}
>
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', marginBottom: 4 }}>
<Typography.Text
strong
style={{ width: 200, marginRight: 8 }}
>
Tên ngư cụ
</Typography.Text>
<Typography.Text strong style={{ width: 200 }}>
Số lượng
</Typography.Text>
</div>
<div style={{ display: 'flex' }}>
<ProFormText
name="name"
fieldProps={{ style: { width: 200, marginRight: 8 } }}
/>
<ProFormText
name="number"
fieldProps={{ style: { width: 200 } }}
/>
</div>
</div>
</ProFormList>
</ProForm.Group>
</StepsForm.StepForm>
{/* Step 4: Chi phí nguyên liệu */}
<StepsForm.StepForm
title="Chi phí nguyên liệu"
name="trip_cost_step"
preserve
>
<ProForm.Group title="Chi phí nguyên liệu">
<ProFormList
name="trip_cost"
creatorButtonProps={{ creatorButtonText: 'Thêm nguyên liệu' }}
copyIconProps={{ tooltipText: 'Sao chép' }}
deleteIconProps={{ tooltipText: 'Xóa' }}
>
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', marginBottom: 4 }}>
<Typography.Text
strong
style={{ width: 120, marginRight: 8 }}
>
Loại
</Typography.Text>
<Typography.Text
strong
style={{ width: 100, marginRight: 8 }}
>
Số lượng
</Typography.Text>
<Typography.Text
strong
style={{ width: 100, marginRight: 8 }}
>
Đơn vị
</Typography.Text>
<Typography.Text
strong
style={{ width: 100, marginRight: 8 }}
>
Chi phí/đơn vị
</Typography.Text>
<Typography.Text strong style={{ width: 100 }}>
Tổng chi phí
</Typography.Text>
</div>
<div style={{ display: 'flex' }}>
<ProFormSelect
name="type"
placeholder="Chọn loại"
fieldProps={{ style: { width: 120, marginRight: 8 } }}
options={[
{ label: 'Nhiên liệu', value: 'fuel' },
{ label: 'Lương thuyền viên', value: 'crew_salary' },
{ label: 'Lương thực', value: 'food' },
{ label: 'Muối đá', value: 'salt_ice' },
]}
rules={[
{ required: true, message: 'Vui lòng chọn loại' },
]}
/>
<ProFormText
name="amount"
fieldProps={{
style: { width: 100, marginRight: 8 },
placeholder: 'Số lượng',
}}
rules={[{ required: true, message: 'Nhập số lượng' }]}
/>
<ProFormText
name="unit"
fieldProps={{
style: { width: 100, marginRight: 8 },
placeholder: 'Đơn vị',
}}
rules={[{ required: true, message: 'Nhập đơn vị' }]}
/>
<ProFormText
name="cost_per_unit"
fieldProps={{
style: { width: 100, marginRight: 8 },
placeholder: 'Chi phí',
}}
rules={[{ required: true, message: 'Nhập chi phí' }]}
/>
<ProFormText
name="total_cost"
fieldProps={{
style: { width: 100 },
placeholder: 'Tổng',
}}
rules={[{ required: true, message: 'Nhập tổng' }]}
/>
</div>
</div>
</ProFormList>
</ProForm.Group>
</StepsForm.StepForm>
</StepsForm>
</ModalForm>
</>
);
};
export default CreateTrip;