refactor(typings): Refactor user and profile models; update API response types
This commit is contained in:
@@ -61,7 +61,7 @@ const ShipDetail = ({
|
||||
|
||||
let shipOwner:
|
||||
| MasterModel.UserResponse
|
||||
| MasterModel.ProfileResponse
|
||||
| MasterModel.UserResponse
|
||||
| null = null;
|
||||
if (resp?.owner_id) {
|
||||
if (initialState?.currentUserProfile?.metadata?.user_type === 'admin') {
|
||||
|
||||
@@ -206,7 +206,7 @@ const MapPage = () => {
|
||||
? ''
|
||||
: [stateCriticalQuery, stateSosQuery].filter(Boolean).join(',');
|
||||
let metaFormQuery: Record<string, any> = {};
|
||||
if (stateQuery?.isDisconected)
|
||||
if (stateQuery?.isDisconnected)
|
||||
metaFormQuery = { ...metaFormQuery, connected: false };
|
||||
const metaStateQuery =
|
||||
stateQueryParams !== '' ? { state_level: stateQueryParams } : null;
|
||||
@@ -252,7 +252,7 @@ const MapPage = () => {
|
||||
};
|
||||
|
||||
setThings(null);
|
||||
const resp = await apiSearchThings(metaQuery);
|
||||
const resp = await apiSearchThings(metaQuery, 'sgw');
|
||||
return resp;
|
||||
} catch (error) {
|
||||
console.error('Error when searchThings: ', error);
|
||||
|
||||
@@ -58,7 +58,7 @@ export type TagStateCallbackPayload = {
|
||||
isWarning: boolean;
|
||||
isCritical: boolean;
|
||||
isSos: boolean;
|
||||
isDisconected: boolean; // giữ đúng theo yêu cầu (1 'n')
|
||||
isDisconnected: boolean; // giữ đúng theo yêu cầu (1 'n')
|
||||
};
|
||||
|
||||
export type PointData = {
|
||||
@@ -82,7 +82,7 @@ export interface GPSParseResult {
|
||||
|
||||
export interface ShipDetailData {
|
||||
ship: SgwModel.ShipDetail;
|
||||
owner: MasterModel.ProfileResponse | null;
|
||||
owner: MasterModel.UserResponse | null;
|
||||
gps: GPSParseResult | null;
|
||||
trip_id?: string;
|
||||
zone_entered_alarm_list: ZoneAlarmParse[];
|
||||
|
||||
@@ -13,7 +13,7 @@ interface EditModalProps {
|
||||
onVisibleChange: (visible: boolean) => void;
|
||||
onFinish: (
|
||||
values: Pick<MasterModel.Thing, 'name'> &
|
||||
Pick<MasterModel.ThingMetadata, 'address' | 'external_id'>,
|
||||
Pick<MasterModel.ThingReponseMetadata, 'address' | 'external_id'>,
|
||||
) => Promise<boolean>;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,18 @@ interface CreateTripProps {
|
||||
thing_id?: string;
|
||||
}
|
||||
|
||||
interface TripFormValues {
|
||||
name: string;
|
||||
departure_time: any; // dayjs object or string
|
||||
departure_port_id: number;
|
||||
arrival_time?: any; // dayjs object or string
|
||||
arrival_port_id?: number;
|
||||
fishing_ground_codes?: number[];
|
||||
fishing_gear?: SgwModel.FishingGear[];
|
||||
trip_cost?: SgwModel.TripCost[];
|
||||
ship_id?: string;
|
||||
}
|
||||
|
||||
const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedThing, setSelectedThing] = useState<string | undefined>();
|
||||
@@ -41,9 +53,9 @@ const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lastTrip, setLastTrip] = useState<SgwModel.Trip | null>(null);
|
||||
const [initialFormValues, setInitialFormValues] = useState<
|
||||
Partial<SgwModel.TripFormValues>
|
||||
Partial<TripFormValues>
|
||||
>({});
|
||||
const formRef = useRef<ProFormInstance<SgwModel.TripFormValues>>(null);
|
||||
const formRef = useRef<ProFormInstance<TripFormValues>>(null);
|
||||
const { homeports, getHomeportsByProvinceCode } = useModel(
|
||||
'slave.sgw.useHomePorts',
|
||||
);
|
||||
@@ -125,7 +137,7 @@ const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: SgwModel.TripFormValues) => {
|
||||
const handleSubmit = async (values: TripFormValues) => {
|
||||
try {
|
||||
if (!selectedShip) {
|
||||
message.error('Vui lòng chọn tàu!');
|
||||
@@ -155,7 +167,7 @@ const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
|
||||
.filter((n: number) => !isNaN(n))
|
||||
: [],
|
||||
fishing_gears: values.fishing_gear,
|
||||
trip_cost: values.trip_cost,
|
||||
trip_cost: values.trip_cost || [],
|
||||
};
|
||||
|
||||
const thingIdToUse = selectedShip.thing_id || selectedThing;
|
||||
@@ -211,7 +223,7 @@ const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
|
||||
>
|
||||
<StepsForm<SgwModel.TripCreateParams>
|
||||
stepsProps={{ size: 'small' }}
|
||||
onFinish={handleSubmit}
|
||||
onFinish={(values) => handleSubmit(values as TripFormValues)}
|
||||
submitter={{
|
||||
render: (_: SubmitterProps, dom) => dom,
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,284 @@
|
||||
import React from 'react';
|
||||
|
||||
import TagState from '@/components/shared/TagState';
|
||||
import {
|
||||
getBadgeConnection,
|
||||
getBadgeStatus,
|
||||
} from '@/components/shared/ThingShared';
|
||||
import TreeGroup from '@/components/shared/TreeGroup';
|
||||
import { DEFAULT_PAGE_SIZE } from '@/constants';
|
||||
import { apiSearchThings } from '@/services/master/ThingController';
|
||||
import {
|
||||
ActionType,
|
||||
ProCard,
|
||||
ProColumns,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { FormattedMessage, useIntl } from '@umijs/max';
|
||||
import { Flex, Grid, theme, Typography } from 'antd';
|
||||
import moment from 'moment';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { TagStateCallbackPayload } from '../../SGW/Map/type';
|
||||
const { Text } = Typography;
|
||||
const SpoleHome: React.FC = () => {
|
||||
const { useBreakpoint } = Grid;
|
||||
const intl = useIntl();
|
||||
const screens = useBreakpoint();
|
||||
const { token } = theme.useToken();
|
||||
const actionRef = useRef<ActionType | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [groupCheckedKeys, setGroupCheckedKeys] = useState<
|
||||
string | string[] | null
|
||||
>(null);
|
||||
const [thing, setThing] = useState<
|
||||
SpoleModel.SpoleThingsResponse | undefined
|
||||
>(undefined);
|
||||
const [stateQuery, setStateQuery] = useState<
|
||||
TagStateCallbackPayload | undefined
|
||||
>(undefined);
|
||||
|
||||
const columns: ProColumns<SpoleModel.SpoleThing>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
title: (
|
||||
<FormattedMessage id="master.devices.name" defaultMessage="Name" />
|
||||
),
|
||||
tip: intl.formatMessage({
|
||||
id: 'master.devices.name.tip',
|
||||
defaultMessage: 'The device name',
|
||||
}),
|
||||
dataIndex: 'name',
|
||||
hideInSearch: true,
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
key: 'connected',
|
||||
hideInSearch: true,
|
||||
ellipsis: true,
|
||||
title: (
|
||||
<FormattedMessage id="common.connect" defaultMessage="Connection" />
|
||||
),
|
||||
dataIndex: ['metadata', 'connected'],
|
||||
filters: [
|
||||
{ text: 'Connected', value: true },
|
||||
{ text: 'Disconnected', value: false },
|
||||
],
|
||||
onFilter: (value: any, row) => row?.metadata?.connected === value,
|
||||
render: (_, row) => {
|
||||
const connectionDuration = row.metadata!.connected
|
||||
? row.metadata!.uptime! * 1000
|
||||
: (Math.round(new Date().getTime() / 1000) -
|
||||
row.metadata!.updated_time!) *
|
||||
1000;
|
||||
|
||||
return (
|
||||
<Flex gap={5}>
|
||||
{getBadgeConnection(row.metadata!.connected || false)}
|
||||
<Text type={row.metadata?.connected ? undefined : 'secondary'}>
|
||||
{connectionDuration > 0
|
||||
? moment.duration(connectionDuration).humanize()
|
||||
: ''}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({
|
||||
id: 'common.status',
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
dataIndex: ['metadata', 'state_level'],
|
||||
key: 'state_level',
|
||||
hideInSearch: true,
|
||||
filters: true,
|
||||
onFilter: true,
|
||||
valueType: 'select',
|
||||
valueEnum: {
|
||||
0: {
|
||||
text: intl.formatMessage({
|
||||
id: 'common.level.normal',
|
||||
defaultMessage: 'Normal',
|
||||
}),
|
||||
status: 'Normal',
|
||||
},
|
||||
1: {
|
||||
text: intl.formatMessage({
|
||||
id: 'common.level.warning',
|
||||
defaultMessage: 'Warning',
|
||||
}),
|
||||
status: 'Warning',
|
||||
},
|
||||
2: {
|
||||
text: intl.formatMessage({
|
||||
id: 'common.level.critical',
|
||||
defaultMessage: 'Critical',
|
||||
}),
|
||||
status: 'Critical',
|
||||
},
|
||||
},
|
||||
render: (_, row) => {
|
||||
const alarm = JSON.parse(row?.metadata?.alarm_list || '{}');
|
||||
const text = alarm?.map((a: any) => a?.name).join(', ');
|
||||
return (
|
||||
<Flex gap={5}>
|
||||
{getBadgeStatus(Number(row?.metadata?.state_level ?? -1))}
|
||||
<Text type={row.metadata?.connected ? undefined : 'secondary'}>
|
||||
{text}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
const handleTagStateChange = (payload: TagStateCallbackPayload) => {
|
||||
setStateQuery(payload);
|
||||
actionRef.current?.reload();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<h1>Trang chủ (Spole)</h1>
|
||||
<ProCard split={screens.md ? 'vertical' : 'horizontal'}>
|
||||
<ProCard colSpan={{ xs: 24, sm: 24, md: 4, lg: 4, xl: 4 }}>
|
||||
<TreeGroup
|
||||
disable={isLoading}
|
||||
multiple={true}
|
||||
groupIds={groupCheckedKeys}
|
||||
onSelected={(value: string | string[] | null) => {
|
||||
setGroupCheckedKeys(value);
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reload();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard colSpan={{ xs: 24, sm: 24, md: 20, lg: 20, xl: 20 }}>
|
||||
<ProTable<MasterModel.Thing>
|
||||
bordered={true}
|
||||
columns={columns}
|
||||
tableLayout="auto"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
search={{
|
||||
layout: 'vertical', // Hoặc 'vertical' để xếp dọc
|
||||
defaultCollapsed: false, // Mặc định mở rộng
|
||||
span: 12, // Chiếm 12 cột trên lưới (tổng 24 cột)
|
||||
filterType: 'light', // Loại filter
|
||||
labelWidth: 'auto', // Độ rộng nhãn
|
||||
}}
|
||||
size="large"
|
||||
dateFormatter="string"
|
||||
pagination={{
|
||||
defaultPageSize: DEFAULT_PAGE_SIZE * 2,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ['10', '15', '20'],
|
||||
showTotal: (total, range) =>
|
||||
`${range[0]}-${range[1]}
|
||||
${intl.formatMessage({
|
||||
id: 'common.paginations.of',
|
||||
defaultMessage: 'of',
|
||||
})}
|
||||
${total} ${intl.formatMessage({
|
||||
id: 'master.devices.table.pagination',
|
||||
defaultMessage: 'devices',
|
||||
})}`,
|
||||
}}
|
||||
request={async (params = {}) => {
|
||||
const {
|
||||
current = 1,
|
||||
pageSize,
|
||||
name,
|
||||
external_id,
|
||||
keyword,
|
||||
} = params;
|
||||
const size = pageSize || DEFAULT_PAGE_SIZE * 2;
|
||||
const offset = current === 1 ? 0 : (current - 1) * size;
|
||||
setIsLoading(true);
|
||||
const stateNormalQuery = stateQuery?.isNormal ? 'normal' : '';
|
||||
const stateSosQuery = stateQuery?.isSos ? 'sos' : '';
|
||||
const stateWarningQuery = stateQuery?.isWarning
|
||||
? stateNormalQuery + ',warning'
|
||||
: stateNormalQuery;
|
||||
const stateCriticalQuery = stateQuery?.isCritical
|
||||
? stateWarningQuery + ',critical'
|
||||
: stateWarningQuery;
|
||||
const stateQueryParams =
|
||||
stateQuery?.isNormal &&
|
||||
stateQuery?.isWarning &&
|
||||
stateQuery?.isCritical &&
|
||||
stateQuery?.isSos
|
||||
? ''
|
||||
: [stateCriticalQuery, stateSosQuery]
|
||||
.filter(Boolean)
|
||||
.join(',');
|
||||
let metaFormQuery: Record<string, any> = {};
|
||||
if (stateQuery?.isDisconnected)
|
||||
metaFormQuery = { ...metaFormQuery, connected: false };
|
||||
const metaStateQuery =
|
||||
stateQueryParams !== ''
|
||||
? { state_level: stateQueryParams }
|
||||
: null;
|
||||
let metadata: Partial<MasterModel.SearchThingMetadata> = {};
|
||||
if (external_id) metadata.external_id = external_id;
|
||||
|
||||
if (metaStateQuery) metadata = { ...metadata, ...metaStateQuery };
|
||||
// Add group filter if groups are selected
|
||||
if (groupCheckedKeys && groupCheckedKeys.length > 0) {
|
||||
const groupId = Array.isArray(groupCheckedKeys)
|
||||
? groupCheckedKeys.join(',')
|
||||
: groupCheckedKeys;
|
||||
metadata.group_id = groupId;
|
||||
}
|
||||
|
||||
const query: MasterModel.SearchThingPaginationBody = {
|
||||
offset: offset,
|
||||
limit: size,
|
||||
order: 'name',
|
||||
dir: 'asc',
|
||||
};
|
||||
if (keyword) query.name = keyword;
|
||||
if (Object.keys(metadata).length > 0) query.metadata = metadata;
|
||||
try {
|
||||
const response = await apiSearchThings(query, 'spole');
|
||||
setIsLoading(false);
|
||||
setThing(response);
|
||||
return {
|
||||
data: response.things || [],
|
||||
success: true,
|
||||
total: response.total || 0,
|
||||
};
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
return {
|
||||
data: [],
|
||||
success: false,
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
search: true,
|
||||
setting: false,
|
||||
density: false,
|
||||
reload: true,
|
||||
}}
|
||||
toolbar={{
|
||||
actions: [
|
||||
<TagState
|
||||
key={'device-state-tag'}
|
||||
normalCount={thing?.metadata?.total_state_level_0 || 0}
|
||||
warningCount={thing?.metadata?.total_state_level_1 || 0}
|
||||
criticalCount={thing?.metadata?.total_state_level_2 || 0}
|
||||
sosCount={undefined}
|
||||
disconnectedCount={
|
||||
(thing?.metadata?.total_thing ?? 0) -
|
||||
(thing?.metadata?.total_connected ?? 0) || 0
|
||||
}
|
||||
onTagPress={handleTagStateChange}
|
||||
/>,
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
</ProCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user