361 lines
11 KiB
TypeScript
361 lines
11 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
|
import { getGPS } from '@/services/controller/DeviceController';
|
|
import {
|
|
getFishSpecies,
|
|
updateFishingLogs,
|
|
} from '@/services/controller/TripController';
|
|
import { getColorByRarityLevel, getRarityById } from '@/utils/fishRarity';
|
|
import { DeleteOutlined } from '@ant-design/icons';
|
|
import { EditableProTable, ProColumns } from '@ant-design/pro-components';
|
|
import { Button, Flex, message, Modal, Tag, Tooltip, Typography } from 'antd';
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
interface CreateOrUpdateFishingLogProps {
|
|
trip: API.Trip;
|
|
fishingLogs?: API.FishingLog;
|
|
isFinished: boolean;
|
|
isOpen: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onFinished?: (success: boolean) => void;
|
|
}
|
|
|
|
interface FishingLogInfoWithKey extends API.FishingLogInfo {
|
|
key: React.Key;
|
|
}
|
|
|
|
const CreateOrUpdateFishingLog: React.FC<CreateOrUpdateFishingLogProps> = ({
|
|
trip,
|
|
fishingLogs,
|
|
isFinished,
|
|
isOpen,
|
|
onOpenChange,
|
|
onFinished,
|
|
}) => {
|
|
const [dataSource, setDataSource] = useState<
|
|
readonly FishingLogInfoWithKey[]
|
|
>([]);
|
|
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
|
const [fishDatas, setFishDatas] = useState<API.FishSpeciesResponse[]>([]);
|
|
const getAllFish = async () => {
|
|
try {
|
|
const resp = await getFishSpecies();
|
|
setFishDatas(resp);
|
|
// console.log('Fetched fish species:', resp);
|
|
} catch (error) {
|
|
console.error('Error fetching fish species:', error);
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
getAllFish();
|
|
if (isOpen) {
|
|
// console.log('Modal opened with fishingLogs:', fishingLogs);
|
|
|
|
if (fishingLogs?.info && fishingLogs.info.length > 0) {
|
|
const dataWithKeys: FishingLogInfoWithKey[] = fishingLogs.info.map(
|
|
(item, index) => ({
|
|
...item,
|
|
key: index,
|
|
}),
|
|
);
|
|
setDataSource(dataWithKeys);
|
|
setEditableRowKeys(dataWithKeys.map((item) => item.key));
|
|
} else {
|
|
// Nếu không có info thì reset table
|
|
setDataSource([]);
|
|
setEditableRowKeys([]);
|
|
}
|
|
}
|
|
}, [isOpen, fishingLogs]);
|
|
|
|
const columns: ProColumns<FishingLogInfoWithKey>[] = [
|
|
{
|
|
title: 'Tên cá',
|
|
dataIndex: 'fish_species_id',
|
|
valueType: 'select',
|
|
fieldProps: {
|
|
showSearch: true,
|
|
options: fishDatas.map((f) => ({
|
|
label: f.name,
|
|
value: f.id,
|
|
data: JSON.stringify(f),
|
|
})),
|
|
optionRender: (option: any) => {
|
|
const fish: API.FishSpeciesResponse = JSON.parse(option.data.data);
|
|
const fishRarity = getRarityById(fish.rarity_level || 1);
|
|
return (
|
|
<Flex align="center" gap={8}>
|
|
<Typography.Text>{fish.name}</Typography.Text>
|
|
<Tooltip title={fishRarity?.rarityDescription || ''}>
|
|
<Tag color={getColorByRarityLevel(fish.rarity_level || 1)}>
|
|
{fishRarity?.rarityLabel}
|
|
</Tag>
|
|
</Tooltip>
|
|
</Flex>
|
|
);
|
|
},
|
|
},
|
|
formItemProps: {
|
|
rules: [{ required: true, message: 'Vui lòng chọn tên cá' }],
|
|
},
|
|
},
|
|
{
|
|
title: 'Kích thước (cm)',
|
|
dataIndex: 'fish_size',
|
|
valueType: 'digit',
|
|
width: '15%',
|
|
formItemProps: {
|
|
rules: [
|
|
{
|
|
required: true,
|
|
message: 'Vui lòng nhập kích thước',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
title: 'Số lượng',
|
|
dataIndex: 'catch_number',
|
|
valueType: 'digit',
|
|
width: '15%',
|
|
formItemProps: {
|
|
rules: [
|
|
{
|
|
required: true,
|
|
message: 'Vui lòng nhập số lượng',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
title: 'Đơn vị',
|
|
dataIndex: 'catch_unit',
|
|
valueType: 'select',
|
|
width: '15%',
|
|
valueEnum: {
|
|
kg: 'kg',
|
|
con: 'con',
|
|
tấn: 'tấn',
|
|
},
|
|
formItemProps: {
|
|
rules: [
|
|
{
|
|
required: true,
|
|
message: 'Vui lòng chọn đơn vị',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
title: 'Thao tác',
|
|
valueType: 'option',
|
|
width: 120,
|
|
render: () => {
|
|
return null;
|
|
},
|
|
},
|
|
];
|
|
|
|
async function createOrCreateOrUpdateFishingLog(
|
|
fishingLog: FishingLogInfoWithKey[],
|
|
) {
|
|
// console.log('Is finished:', isFinished);
|
|
// console.log('Trip:', trip);
|
|
|
|
try {
|
|
const gpsData = await getGPS();
|
|
if (gpsData) {
|
|
if (isFinished === false) {
|
|
// Tạo mẻ mới
|
|
const logStatus0 = trip.fishing_logs?.find((log) => log.status === 0);
|
|
// console.log('ok', logStatus0);
|
|
// console.log('ok', fishingLog);
|
|
const body: API.FishingLog = {
|
|
fishing_log_id: logStatus0?.fishing_log_id || '',
|
|
trip_id: trip.id,
|
|
start_at: logStatus0?.start_at!,
|
|
start_lat: logStatus0?.start_lat!,
|
|
start_lon: logStatus0?.start_lon!,
|
|
haul_lat: gpsData.lat,
|
|
haul_lon: gpsData.lon,
|
|
end_at: new Date(),
|
|
status: 1,
|
|
weather_description: logStatus0?.weather_description || '',
|
|
info: fishingLog.map((item) => ({
|
|
fish_species_id: item.fish_species_id,
|
|
fish_name: item.fish_name,
|
|
catch_number: item.catch_number,
|
|
catch_unit: item.catch_unit,
|
|
fish_size: item.fish_size,
|
|
fish_rarity: item.fish_rarity,
|
|
fish_condition: '',
|
|
gear_usage: '',
|
|
})),
|
|
sync: true,
|
|
};
|
|
await updateFishingLogs(body);
|
|
// console.log('Resp', resp);
|
|
|
|
onFinished?.(true);
|
|
onOpenChange(false);
|
|
} else {
|
|
const body: API.FishingLog = {
|
|
fishing_log_id: fishingLogs?.fishing_log_id || '',
|
|
trip_id: fishingLogs?.trip_id!,
|
|
start_at: fishingLogs?.start_at!,
|
|
start_lat: fishingLogs?.start_lat!,
|
|
start_lon: fishingLogs?.start_lon!,
|
|
haul_lat: fishingLogs?.haul_lat!,
|
|
haul_lon: fishingLogs?.haul_lon!,
|
|
end_at: fishingLogs?.end_at!,
|
|
status: fishingLogs?.status!,
|
|
weather_description: fishingLogs?.weather_description || '',
|
|
info: fishingLog.map((item) => ({
|
|
fish_species_id: item.fish_species_id,
|
|
fish_name: item.fish_name,
|
|
catch_number: item.catch_number,
|
|
catch_unit: item.catch_unit,
|
|
fish_size: item.fish_size,
|
|
fish_rarity: item.fish_rarity,
|
|
fish_condition: '',
|
|
gear_usage: '',
|
|
})),
|
|
sync: true,
|
|
};
|
|
// console.log('Update body:', body);
|
|
|
|
await updateFishingLogs(body);
|
|
// console.log('Resp', resp);
|
|
|
|
onFinished?.(true);
|
|
onOpenChange(false);
|
|
}
|
|
setDataSource([]);
|
|
setEditableRowKeys([]);
|
|
} else {
|
|
message.error('Không thể lấy dữ liệu GPS. Vui lòng thử lại.');
|
|
}
|
|
} catch (error) {
|
|
onFinished?.(false);
|
|
console.error('Error creating/updating haul:', error);
|
|
}
|
|
}
|
|
return (
|
|
<Modal
|
|
width="70%"
|
|
title={isFinished ? 'Cập nhật mẻ lưới' : 'Kết thúc mẻ lưới'}
|
|
open={isOpen}
|
|
cancelText="Huỷ"
|
|
maskClosable={false}
|
|
okText={isFinished ? 'Cập nhật' : 'Kết thúc'}
|
|
onCancel={() => onOpenChange(false)}
|
|
onOk={async () => {
|
|
// Validate data trước khi submit
|
|
const validData = dataSource.filter(
|
|
(item) =>
|
|
item.fish_name &&
|
|
item.fish_size &&
|
|
item.catch_number &&
|
|
item.catch_unit,
|
|
);
|
|
|
|
if (validData.length === 0) {
|
|
message.error(
|
|
'Vui lòng nhập ít nhất một loài cá với đầy đủ thông tin',
|
|
);
|
|
return;
|
|
}
|
|
await createOrCreateOrUpdateFishingLog(validData);
|
|
}}
|
|
>
|
|
<EditableProTable<FishingLogInfoWithKey>
|
|
key={fishingLogs?.fishing_log_id}
|
|
headerTitle="Danh sách cá đánh bắt"
|
|
columns={columns}
|
|
rowKey="key"
|
|
scroll={{
|
|
x: 960,
|
|
}}
|
|
value={dataSource}
|
|
onChange={setDataSource}
|
|
recordCreatorProps={{
|
|
newRecordType: 'dataSource',
|
|
creatorButtonText: 'Thêm loài',
|
|
record: () => ({
|
|
key: Date.now(),
|
|
}),
|
|
}}
|
|
editable={{
|
|
type: 'multiple',
|
|
editableKeys,
|
|
actionRender: (row, config, defaultDoms) => {
|
|
return [defaultDoms.delete];
|
|
},
|
|
deletePopconfirmMessage: 'Bạn chắc chắn muốn xoá?',
|
|
onValuesChange: (
|
|
record: Partial<FishingLogInfoWithKey> | undefined,
|
|
recordList: FishingLogInfoWithKey[],
|
|
) => {
|
|
// Nếu không có record (sự kiện không liên quan tới 1 dòng cụ thể) thì chỉ cập nhật dataSource
|
|
if (!record) {
|
|
setDataSource([...recordList]);
|
|
return;
|
|
}
|
|
|
|
// Lấy giá trị species id (cẩn trọng string/number)
|
|
const speciesId = (record as any).fish_species_id;
|
|
if (speciesId === undefined || speciesId === null) {
|
|
// Nếu không phải là thay đổi chọn loài cá, chỉ cập nhật dataSource
|
|
setDataSource([...recordList]);
|
|
return;
|
|
}
|
|
|
|
// Tìm loài cá tương ứng, so sánh bằng String để tránh khác kiểu number/string
|
|
const fish = fishDatas.find(
|
|
(f) => String(f.id) === String(speciesId),
|
|
);
|
|
if (!fish) {
|
|
setDataSource([...recordList]);
|
|
return;
|
|
}
|
|
|
|
// Tạo record mới (merge thông tin loài cá vào dòng hiện tại)
|
|
const mergedRecord: FishingLogInfoWithKey = {
|
|
...(record as FishingLogInfoWithKey),
|
|
fish_species_id: fish.id,
|
|
fish_name: fish.name,
|
|
catch_unit: fish.default_unit,
|
|
fish_rarity: fish.rarity_level,
|
|
};
|
|
|
|
// Áp lại vào recordList dựa theo key (so khớp key bằng String để an toàn)
|
|
const newList = recordList.map((r) =>
|
|
String(r.key) === String(mergedRecord.key) ? mergedRecord : r,
|
|
);
|
|
|
|
// Cập nhật state (sao chép mảng để tránh vấn đề readonly/type)
|
|
setDataSource([...newList]);
|
|
|
|
// Đảm bảo dòng này đang ở trạng thái editable (nếu cần)
|
|
setEditableRowKeys((prev) =>
|
|
prev.includes(mergedRecord.key)
|
|
? prev
|
|
: [...prev, mergedRecord.key],
|
|
);
|
|
},
|
|
onChange: setEditableRowKeys,
|
|
deleteText: (
|
|
<Button
|
|
type="primary"
|
|
danger
|
|
shape="circle"
|
|
icon={<DeleteOutlined />}
|
|
/>
|
|
),
|
|
}}
|
|
/>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default CreateOrUpdateFishingLog;
|