Files
FE-DEVICE-SGW/src/pages/Trip/components/CreateOrUpdateFishingLog.tsx
2025-09-26 18:22:04 +07:00

360 lines
11 KiB
TypeScript

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[]>([]);
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 getAllFish = async () => {
try {
const resp = await getFishSpecies();
setFishDatas(resp);
console.log('Fetched fish species:', resp);
} catch (error) {
console.error('Error fetching fish species:', error);
}
};
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,
};
const resp = 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);
const resp = 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;