import IconFont from '@/components/IconFont'; import TreeGroup from '@/components/shared/TreeGroup'; import { DEFAULT_PAGE_SIZE } from '@/const'; import { SGW_ROUTE_BANZONES, SGW_ROUTE_BANZONES_LIST, } from '@/constants/slave/sgw/routes'; import { apiGetAllBanzones, apiRemoveBanzone, } from '@/services/slave/sgw/ZoneController'; import { flattenGroupNodes } from '@/utils/slave/sgw/groupUtils'; import { formatDate } from '@/utils/slave/sgw/timeUtils'; import { DeleteOutlined, DownOutlined, EditOutlined } from '@ant-design/icons'; import { ActionType, ProCard, ProColumns, ProTable, } from '@ant-design/pro-components'; import { FormattedMessage, history, useIntl, useModel } from '@umijs/max'; import { Button, Dropdown, Flex, Grid, message, Popconfirm, Space, Tag, Tooltip, Typography, } from 'antd'; import { MenuProps } from 'antd/lib'; import { useEffect, useRef, useState } from 'react'; const { Paragraph, Text } = Typography; const BanZoneList = () => { const { useBreakpoint } = Grid; const intl = useIntl(); const screens = useBreakpoint(); const tableRef = useRef(); const [groupCheckedKeys, setGroupCheckedKeys] = useState(''); const { groups, getGroups } = useModel('master.useGroups'); const groupFlattened = flattenGroupNodes(groups || []); const [messageApi, contextHolder] = message.useMessage(); const [selectedRowsState, setSelectedRows] = useState([]); useEffect(() => { if (groups === null) { getGroups(); } }, [groups]); // Reload table khi groups được load useEffect(() => { if (groups && groups.length > 0 && tableRef.current) { tableRef.current.reload(); } }, [groups]); const handleEdit = (record: SgwModel.Banzone) => { console.log('record: ', record); let geomType = 1; // Default: Polygon try { if (record.geometry) { const geometry: SgwModel.Geom = JSON.parse(record.geometry); geomType = geometry.geom_type || 1; } } catch (e) { console.error('Failed to parse geometry:', e); } history.push(`${SGW_ROUTE_BANZONES_LIST}/${record.id}`, { type: 'update', shape: geomType, }); }; const handleDelete = async (record: SgwModel.Banzone) => { try { const groupID = groupFlattened.find( (m) => m.metadata.code === record.province_code, )?.id; await apiRemoveBanzone(record.id || '', groupID || ''); messageApi.success( intl.formatMessage({ id: 'banzone.notify.delete_zone_success', defaultMessage: 'Zone deleted successfully', }), ); // Reload lại bảng if (tableRef.current) { tableRef.current.reload(); } } catch (error) { console.error('Error deleting area:', error); messageApi.error( intl.formatMessage({ id: 'banzone.notify.fail', defaultMessage: 'Delete zone failed!', }), ); } }; const columns: ProColumns[] = [ { key: 'name', title: , dataIndex: 'name', render: (_, record) => (
{record?.name}
), width: '15%', }, { key: 'group', title: , dataIndex: 'province_code', hideInSearch: true, responsive: ['lg', 'md'], ellipsis: true, render: (_, record) => { const matchedMember = groupFlattened.find( (group) => group.metadata.code === record.province_code, ) ?? null; return ( {matchedMember?.name || '-'} ); }, width: '15%', }, { key: 'description', title: ( ), dataIndex: 'description', hideInSearch: true, render: (_, record) => ( {record?.description || '-'} ), width: '15%', }, { key: 'type', title: , dataIndex: 'type', valueType: 'select', fieldProps: { options: [ { label: intl.formatMessage({ id: 'banzone.area.fishing_ban', defaultMessage: 'Fishing Ban', }), value: 1, }, { label: intl.formatMessage({ id: 'banzone.area.move_ban', defaultMessage: 'Movement Ban', }), value: 2, }, { label: intl.formatMessage({ id: 'banzone.area.safe', defaultMessage: 'Safe Area', }), value: 3, }, ], }, render: (_, record) => ( {record.type === 1 ? intl.formatMessage({ id: 'banzone.area.fishing_ban', defaultMessage: 'Fishing Ban', }) : intl.formatMessage({ id: 'banzone.area.move_ban', defaultMessage: 'Movement Ban', })} ), width: 120, }, { key: 'conditions', title: ( ), dataIndex: 'conditions', hideInSearch: true, render: (conditions) => { if (!Array.isArray(conditions)) return null; return ( {conditions.map((cond, index) => { switch (cond.type) { case 'month_range': return ( Th.{cond.from} - Th.{cond.to} ); case 'date_range': return ( {formatDate(cond.from)} → {formatDate(cond.to)} ); case 'length_limit': return ( {cond.min}-{cond.max}m ); default: return null; } })} ); }, width: 180, }, { key: 'enabled', title: ( ), dataIndex: 'enabled', valueType: 'select', fieldProps: { options: [ { label: intl.formatMessage({ id: 'banzone.is_enable', defaultMessage: 'Enabled', }), value: true, }, { label: intl.formatMessage({ id: 'banzone.is_unenabled', defaultMessage: 'Disabled', }), value: false, }, ], }, hideInSearch: false, responsive: ['lg', 'md'], render: (_, record) => { return ( {record.enabled === true ? intl.formatMessage({ id: 'banzone.is_enable', defaultMessage: 'Enabled', }) : intl.formatMessage({ id: 'banzone.is_unenabled', defaultMessage: 'Disabled', })} ); }, width: 120, }, { title: , hideInSearch: true, width: 120, fixed: 'right', render: (_, record) => [ handleDelete(record)} okText={intl.formatMessage({ id: 'common.delete', defaultMessage: 'Delete', })} cancelText={intl.formatMessage({ id: 'common.cancel', defaultMessage: 'Cancel', })} okType="danger" > , ], }, ]; const items: Required['items'] = [ { label: intl.formatMessage({ id: 'banzone.polygon', defaultMessage: 'Polygon', }), onClick: () => { history.push(SGW_ROUTE_BANZONES, { shape: 1, type: 'create', }); }, key: '0', icon: , }, { label: intl.formatMessage({ id: 'banzone.polyline', defaultMessage: 'Polyline', }), key: '1', onClick: () => { history.push(SGW_ROUTE_BANZONES, { shape: 2, type: 'create', }); }, icon: , }, { label: intl.formatMessage({ id: 'banzone.circle', defaultMessage: 'Circle', }), key: '3', onClick: () => { history.push(SGW_ROUTE_BANZONES, { shape: 3, type: 'create', }); }, icon: , }, ]; const deleteMultipleBanzones = async (records: SgwModel.Banzone[]) => { const key = 'deleteMultiple'; messageApi.open({ key, type: 'loading', content: intl.formatMessage({ id: 'common.deleting', defaultMessage: 'Deleting...', }), duration: 0, }); try { for (const record of records) { const groupID = groupFlattened.find( (m) => m.metadata.code === record.province_code, )?.id; await apiRemoveBanzone(record.id || '', groupID || ''); } messageApi.open({ key, type: 'success', content: `Đã xoá thành công ${records.length} khu vực`, duration: 2, }); tableRef.current?.reload(); } catch (error) { console.error('Error deleting area:', error); messageApi.open({ key, type: 'error', content: intl.formatMessage({ id: 'banzone.notify.fail', defaultMessage: 'Delete zone failed!', }), duration: 2, }); } }; return ( <> {contextHolder} { // Convert group IDs to province codes string const selectedIds = Array.isArray(value) ? value : value ? [value] : []; const provinceCodes = selectedIds.length > 0 ? selectedIds .reduce((codes: string[], id) => { const group = groupFlattened.find((g) => g.id === id); if (group?.metadata?.code) { codes.push(group.metadata.code); } return codes; }, []) .join(',') : ''; setGroupCheckedKeys(provinceCodes); tableRef.current?.reload(); }} /> tableLayout="fixed" scroll={{ x: 1000 }} actionRef={tableRef} columns={columns} pagination={{ defaultPageSize: DEFAULT_PAGE_SIZE, showSizeChanger: true, pageSizeOptions: ['5', '10', '15', '20'], showTotal: (total, range) => `${range[0]}-${range[1]} ${intl.formatMessage({ id: 'common.of', defaultMessage: 'of', })} ${total} ${intl.formatMessage({ id: 'banzones.title', defaultMessage: 'zones', })}`, }} request={async (params) => { const { current, pageSize, name, type, enabled } = params; // Nếu chưa có groups, đợi if (!groups || groups.length === 0) { return { success: true, data: [], total: 0, }; } const offset = current === 1 ? 0 : (current! - 1) * pageSize!; const groupFalttened = flattenGroupNodes(groups || []); const groupId = groupCheckedKeys || groupFalttened .map((group) => group.metadata.code) .filter(Boolean) .join(',') + ','; if (!groupId || groupId === ',') { return { success: true, data: [], total: 0, }; } const body: SgwModel.SearchZonePaginationBody = { name: name || '', order: 'name', dir: 'asc', limit: pageSize, offset: offset, metadata: { province_code: groupId, ...(type ? { type: Number(type) } : {}), // nếu có type thì thêm vào ...(enabled !== undefined ? { enabled } : {}), }, }; try { const resp = await apiGetAllBanzones(body); return { success: true, data: resp?.banzones || [], total: resp?.total || 0, }; } catch (error) { console.error('Query banzones failed:', error); return { success: true, data: [], total: 0, }; } }} rowKey="id" options={{ search: false, setting: false, density: false, reload: true, }} search={{ layout: 'vertical', defaultCollapsed: false, }} dateFormatter="string" rowSelection={{ selectedRowKeys: selectedRowsState?.map((row) => row?.id ?? ''), onChange: (_, selectedRows) => { setSelectedRows(selectedRows); }, }} tableAlertRender={({ selectedRowKeys }) => (
Đã chọn {selectedRowKeys.length} mục
)} tableAlertOptionRender={({ selectedRows, onCleanSelected }) => { return ( { deleteMultipleBanzones(selectedRows); }} okText={intl.formatMessage({ id: 'common.sure', defaultMessage: 'Chắc chắn', })} cancelText={intl.formatMessage({ id: 'common.no', defaultMessage: 'Không', })} > ); }} toolBarRender={() => [ , ]} />
); }; export default BanZoneList;