feat(core): sgw-device-ui
This commit is contained in:
359
src/pages/Trip/components/CreateOrUpdateFishingLog.tsx
Normal file
359
src/pages/Trip/components/CreateOrUpdateFishingLog.tsx
Normal file
@@ -0,0 +1,359 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user