491 lines
14 KiB
TypeScript
491 lines
14 KiB
TypeScript
import { DeleteButton, EditButton } from '@/components/shared/Button';
|
|
import { DEFAULT_PAGE_SIZE } from '@/constants';
|
|
import {
|
|
apiAddShip,
|
|
apiDeleteShip,
|
|
apiQueryShips,
|
|
apiUpdateShip,
|
|
} from '@/services/slave/sgw/ShipController';
|
|
import { PlusOutlined } from '@ant-design/icons';
|
|
import ProCard from '@ant-design/pro-card';
|
|
import ProDescriptions from '@ant-design/pro-descriptions';
|
|
import { FooterToolbar } from '@ant-design/pro-layout';
|
|
import ProTable, { ActionType, ProColumns } from '@ant-design/pro-table';
|
|
import { FormattedMessage, useIntl, useModel } from '@umijs/max';
|
|
import { Button, Drawer, message, Typography } from 'antd';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import EditModal from './components/EditModal';
|
|
import FormAdd from './components/FormAdd';
|
|
|
|
const { Paragraph } = Typography;
|
|
|
|
type ShipFormValues = Partial<SgwModel.ShipDetail>;
|
|
|
|
type ShipFormAdd = Partial<SgwModel.ShipCreateParams>;
|
|
|
|
const ManagerShips: React.FC = () => {
|
|
const intl = useIntl();
|
|
const { initialState } = useModel('@@initialState');
|
|
const { currentUserProfile } = initialState || {};
|
|
console.log('Current User Profile:', currentUserProfile);
|
|
const [responsive] = useState<boolean>(false);
|
|
const [updateModalVisible, handleUpdateModalVisible] =
|
|
useState<boolean>(false);
|
|
const [createModalVisible, handleCreateModalVisible] =
|
|
useState<boolean>(false);
|
|
const [showDetail, setShowDetail] = useState<boolean>(false);
|
|
const [currentRow, setCurrentRow] = useState<SgwModel.ShipDetail | null>(
|
|
null,
|
|
);
|
|
const actionRef = useRef<ActionType | null>(null);
|
|
const [messageApi, contextHolder] = message.useMessage();
|
|
const [selectedRowsState, setSelectedRowsState] = useState<
|
|
SgwModel.ShipDetail[]
|
|
>([]);
|
|
const { shipTypes, getShipTypes } = useModel('slave.sgw.useShipTypes');
|
|
const { homeports, getHomeportsByProvinceCode } = useModel(
|
|
'slave.sgw.useHomePorts',
|
|
);
|
|
useEffect(() => {
|
|
if (!shipTypes) {
|
|
getShipTypes();
|
|
}
|
|
}, [shipTypes]);
|
|
useEffect(() => {
|
|
getHomeportsByProvinceCode();
|
|
}, [getHomeportsByProvinceCode]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
setSelectedRowsState([]);
|
|
setCurrentRow(null);
|
|
actionRef.current = null;
|
|
};
|
|
}, []);
|
|
|
|
const handleAdd = async (fields: ShipFormAdd) => {
|
|
const key = 'add_ship';
|
|
const {
|
|
reg_number,
|
|
name, // Changed from shipname to name
|
|
thing_id,
|
|
ship_type,
|
|
ship_length,
|
|
ship_power,
|
|
ship_group_id,
|
|
home_port,
|
|
fishing_license_number,
|
|
fishing_license_expiry_date,
|
|
} = fields;
|
|
console.log('Fields received from form:', fields);
|
|
|
|
if (!thing_id || thing_id.length === 0) {
|
|
message.error('Vui lòng chọn một thiết bị để liên kết.');
|
|
return false;
|
|
}
|
|
|
|
const newShip = {
|
|
name: name, // Use name directly
|
|
reg_number: reg_number,
|
|
thing_id: thing_id,
|
|
ship_type: ship_type,
|
|
home_port: home_port,
|
|
fishing_license_number: fishing_license_number,
|
|
fishing_license_expiry_date: fishing_license_expiry_date,
|
|
ship_length: Number(ship_length),
|
|
ship_power: Number(ship_power),
|
|
ship_group_id: ship_group_id,
|
|
};
|
|
|
|
try {
|
|
messageApi.open({
|
|
type: 'loading',
|
|
content: intl.formatMessage({
|
|
id: 'pages.things.creating',
|
|
defaultMessage: 'creating...',
|
|
}),
|
|
duration: 0,
|
|
key,
|
|
});
|
|
|
|
const id = await apiAddShip({ ...newShip });
|
|
if (id) {
|
|
messageApi.open({
|
|
type: 'success',
|
|
content: intl.formatMessage({
|
|
id: 'pages.things.add.success',
|
|
defaultMessage: 'Added successfully and will refresh soon',
|
|
}),
|
|
key,
|
|
});
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (error) {
|
|
messageApi.open({
|
|
type: 'error',
|
|
content: intl.formatMessage({
|
|
id: 'pages.things.add.failed',
|
|
defaultMessage: 'Adding failed, please try again!',
|
|
}),
|
|
key,
|
|
});
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const handleRemove = async (
|
|
selectedRows: SgwModel.ShipDetail[],
|
|
): Promise<boolean> => {
|
|
const key = 'remove_ship';
|
|
if (!selectedRows) return true;
|
|
try {
|
|
messageApi.open({
|
|
type: 'loading',
|
|
content: intl.formatMessage({
|
|
id: 'pages.ships.deleting',
|
|
defaultMessage: 'Deleting...',
|
|
}),
|
|
duration: 0,
|
|
key,
|
|
});
|
|
const allDelete = selectedRows.map(async (row) => {
|
|
if (row.id) await apiDeleteShip(row.id);
|
|
});
|
|
await Promise.all(allDelete);
|
|
messageApi.open({
|
|
type: 'success',
|
|
content: intl.formatMessage({
|
|
id: 'pages.ships.delete.success',
|
|
defaultMessage: 'Deleted successfully and will refresh soon',
|
|
}),
|
|
key,
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
messageApi.open({
|
|
type: 'error',
|
|
content: intl.formatMessage({
|
|
id: 'pages.ships.delete.failed',
|
|
defaultMessage: 'Delete failed, please try again!',
|
|
}),
|
|
key,
|
|
});
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const handleUpdate = async (values: ShipFormValues): Promise<boolean> => {
|
|
const key = 'update_ship';
|
|
try {
|
|
messageApi.open({
|
|
type: 'loading',
|
|
content: intl.formatMessage({
|
|
id: 'pages.ships.updating',
|
|
defaultMessage: 'Updating...',
|
|
}),
|
|
duration: 0,
|
|
key,
|
|
});
|
|
// Fix: loại bỏ ship_group_id nếu là null
|
|
const patch = { ...values };
|
|
if (patch.ship_group_id === null) delete patch.ship_group_id;
|
|
if (!currentRow?.id) throw new Error('Missing ship id');
|
|
await apiUpdateShip(currentRow.id, patch as SgwModel.ShipUpdateParams);
|
|
messageApi.open({
|
|
type: 'success',
|
|
content: intl.formatMessage({
|
|
id: 'pages.ships.update.success',
|
|
defaultMessage: 'Updated successfully and will refresh soon',
|
|
}),
|
|
key,
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
messageApi.open({
|
|
type: 'error',
|
|
content: intl.formatMessage({
|
|
id: 'pages.ships.update.failed',
|
|
defaultMessage: 'Update failed, please try again!',
|
|
}),
|
|
key,
|
|
});
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const columns: ProColumns<SgwModel.ShipDetail, 'text'>[] = [
|
|
{
|
|
key: 'reg_number',
|
|
title: (
|
|
<FormattedMessage
|
|
id="pages.ships.reg_number"
|
|
defaultMessage="Registration Number"
|
|
/>
|
|
),
|
|
dataIndex: 'reg_number',
|
|
render: (_, record) => (
|
|
<a
|
|
style={{ color: '#1890ff', cursor: 'pointer' }}
|
|
onClick={() => {
|
|
setCurrentRow(record);
|
|
setShowDetail(true);
|
|
}}
|
|
>
|
|
{record?.reg_number}
|
|
</a>
|
|
),
|
|
},
|
|
{
|
|
key: 'name',
|
|
title: (
|
|
<FormattedMessage id="pages.ships.name" defaultMessage="Ship Name" />
|
|
),
|
|
dataIndex: 'name',
|
|
render: (dom, record) => (
|
|
<Paragraph
|
|
style={{
|
|
marginBottom: 0,
|
|
verticalAlign: 'middle',
|
|
display: 'inline-block',
|
|
}}
|
|
copyable
|
|
>
|
|
{record?.name}
|
|
</Paragraph>
|
|
),
|
|
},
|
|
{
|
|
key: 'ship_type',
|
|
title: <FormattedMessage id="pages.ships.type" defaultMessage="Type" />,
|
|
dataIndex: 'ship_type',
|
|
render: (dom, record) => {
|
|
const typeObj = Array.isArray(shipTypes)
|
|
? shipTypes.find((t) => t.id === record?.ship_type)
|
|
: undefined;
|
|
return typeObj?.name || record?.ship_type || '-';
|
|
},
|
|
},
|
|
{
|
|
key: 'home_port',
|
|
title: (
|
|
<FormattedMessage
|
|
id="pages.ships.home_port"
|
|
defaultMessage="Home Port"
|
|
/>
|
|
),
|
|
dataIndex: 'home_port',
|
|
hideInSearch: true,
|
|
render: (dom, record) => {
|
|
const portObj = Array.isArray(homeports)
|
|
? homeports.find((p) => p.id === record?.home_port)
|
|
: undefined;
|
|
return portObj?.name || record?.home_port || '-';
|
|
},
|
|
},
|
|
{
|
|
title: (
|
|
<FormattedMessage id="pages.ships.option" defaultMessage="Operating" />
|
|
),
|
|
hideInSearch: true,
|
|
render: (dom, record) => (
|
|
<EditButton
|
|
text={intl.formatMessage({
|
|
id: 'pages.ships.edit.text',
|
|
defaultMessage: 'Edit',
|
|
})}
|
|
onClick={() => {
|
|
setCurrentRow(record);
|
|
handleUpdateModalVisible(true);
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
];
|
|
|
|
// Định nghĩa tạm detailColumns cho ProDescriptions
|
|
const detailColumns = [
|
|
{ title: 'Tên tàu', dataIndex: 'name' },
|
|
{ title: 'Chiều dài tàu', dataIndex: 'ship_length' },
|
|
{ title: 'Công suất tàu', dataIndex: 'ship_power' },
|
|
{ title: 'Đội tàu', dataIndex: 'group_ship' },
|
|
{ title: 'Ảnh tàu', dataIndex: 'reg_number' },
|
|
];
|
|
|
|
return (
|
|
<>
|
|
{contextHolder}
|
|
<ProCard
|
|
split={responsive ? 'horizontal' : 'vertical'}
|
|
style={{ minHeight: 560 }}
|
|
>
|
|
<ProCard colSpan={{ xs: 24, sm: 24, md: 24, lg: 24, xl: 24 }}>
|
|
<ProTable
|
|
actionRef={actionRef}
|
|
columns={columns}
|
|
tableLayout="auto"
|
|
rowKey="id"
|
|
search={{ layout: 'vertical', defaultCollapsed: false }}
|
|
dateFormatter="string"
|
|
rowSelection={{
|
|
selectedRowKeys: selectedRowsState
|
|
.map((row) => row.id!)
|
|
.filter(Boolean),
|
|
onChange: (
|
|
_: React.Key[],
|
|
selectedRows: SgwModel.ShipDetail[],
|
|
) => {
|
|
setSelectedRowsState(selectedRows);
|
|
},
|
|
}}
|
|
pagination={{ pageSize: DEFAULT_PAGE_SIZE * 2 }}
|
|
request={async (params = {}) => {
|
|
const {
|
|
current = 1,
|
|
pageSize,
|
|
name,
|
|
registration_number,
|
|
} = params as {
|
|
current?: number;
|
|
pageSize?: number;
|
|
name?: string;
|
|
registration_number?: string;
|
|
};
|
|
const size = pageSize || DEFAULT_PAGE_SIZE * 2;
|
|
const offset = current === 1 ? 0 : (current - 1) * size;
|
|
const query: SgwModel.ShipQueryParams = {
|
|
offset: offset,
|
|
limit: size,
|
|
order: 'name',
|
|
dir: 'asc',
|
|
name,
|
|
registration_number,
|
|
};
|
|
const resp = await apiQueryShips(query);
|
|
return {
|
|
data: resp.ships || [],
|
|
success: true,
|
|
total: resp.total || 0,
|
|
};
|
|
}}
|
|
toolBarRender={() => {
|
|
return [
|
|
<Button
|
|
type="primary"
|
|
key="primary"
|
|
onClick={() => handleCreateModalVisible(true)}
|
|
>
|
|
<PlusOutlined />{' '}
|
|
<FormattedMessage
|
|
id="pages.ship.create.text"
|
|
defaultMessage="New"
|
|
/>
|
|
</Button>,
|
|
];
|
|
}}
|
|
/>
|
|
</ProCard>
|
|
</ProCard>
|
|
|
|
<FormAdd
|
|
visible={createModalVisible}
|
|
onVisibleChange={handleCreateModalVisible}
|
|
onSubmit={async (values: ShipFormAdd) => {
|
|
console.log('index.tsx onSubmit called with values:', values);
|
|
const success = await handleAdd(values);
|
|
console.log('index.tsx onSubmit - success:', success);
|
|
if (success) {
|
|
handleCreateModalVisible(false);
|
|
if (actionRef.current) {
|
|
actionRef.current.reload();
|
|
}
|
|
}
|
|
return success;
|
|
}}
|
|
/>
|
|
|
|
{currentRow && (
|
|
<EditModal
|
|
visible={updateModalVisible}
|
|
values={currentRow}
|
|
onVisibleChange={(visible) => {
|
|
handleUpdateModalVisible(visible);
|
|
if (!visible) setCurrentRow(null);
|
|
}}
|
|
onFinish={async (values: ShipFormValues) => {
|
|
const success = await handleUpdate(values);
|
|
if (success) {
|
|
handleUpdateModalVisible(false);
|
|
setCurrentRow(null);
|
|
actionRef.current?.reload();
|
|
}
|
|
return success;
|
|
}}
|
|
/>
|
|
)}
|
|
{selectedRowsState?.length > 0 && (
|
|
<FooterToolbar
|
|
extra={
|
|
<div>
|
|
<FormattedMessage
|
|
id="pages.ships.chosen"
|
|
defaultMessage="Chosen"
|
|
/>{' '}
|
|
<a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '}
|
|
<FormattedMessage id="pages.ships.item" defaultMessage="item" />
|
|
</div>
|
|
}
|
|
>
|
|
<DeleteButton
|
|
title={intl.formatMessage({
|
|
id: 'pages.ships.deletion.title',
|
|
defaultMessage: 'Are you sure to delete these selected ships?',
|
|
})}
|
|
text={intl.formatMessage({
|
|
id: 'pages.ships.deletion.text',
|
|
defaultMessage: 'Batch deletion',
|
|
})}
|
|
onOk={async () => {
|
|
const success = await handleRemove(selectedRowsState);
|
|
if (success) {
|
|
setSelectedRowsState([]);
|
|
if (actionRef.current) {
|
|
actionRef.current.reload();
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
</FooterToolbar>
|
|
)}
|
|
<Drawer
|
|
width={600}
|
|
visible={showDetail}
|
|
title={
|
|
<FormattedMessage
|
|
id="pages.ships.detail"
|
|
defaultMessage="Ship Detail"
|
|
/>
|
|
}
|
|
onClose={() => {
|
|
setShowDetail(false);
|
|
setCurrentRow(null);
|
|
}}
|
|
>
|
|
<ProDescriptions<SgwModel.ShipDetail>
|
|
column={1}
|
|
bordered
|
|
request={async () => ({
|
|
data: currentRow ? [currentRow] : [],
|
|
success: true,
|
|
})}
|
|
params={{ id: currentRow?.id }}
|
|
columns={detailColumns}
|
|
/>
|
|
</Drawer>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ManagerShips;
|