refactor(typings): Refactor user and profile models; update API response types

This commit is contained in:
Tran Anh Tuan
2026-01-24 18:18:16 +07:00
parent 1a06328c77
commit b0b09a86b7
43 changed files with 1035 additions and 967 deletions

View File

@@ -1,185 +0,0 @@
# ✅ Zone (Banzone) API Migration - Complete
## Migration Banzone/Zone API vào SGW Module
### Files Created
1. **ZoneController.ts** - `src/services/slave/sgw/ZoneController.ts`
```typescript
✅ apiGetAllBanzones(body) - Get all banzones with pagination
✅ apiRemoveBanzone(id, groupID) - Remove a banzone
✅ apiGetZoneById(zoneId) - Get banzone by ID
✅ apiCreateBanzone(body) - Create new banzone
✅ apiUpdateBanzone(id, body) - Update banzone
```
2. **Type Definitions** - Added to `src/services/slave/sgw/sgw.typing.d.ts`
```typescript
✅ Banzone
✅ Condition
✅ Geom
✅ ZoneResponse
✅ ZoneBodyRequest
```
3. **Route Constants** - Added to `src/constants/slave/sgw/routes.ts`
```typescript
✅ SGW_ROUTE_BANZONES = '/api/sgw/banzones'
✅ SGW_ROUTE_BANZONES_LIST = '/api/sgw/banzones/list'
```
### Files Updated
- ✅ `src/pages/Slave/SGW/Map/components/ShipDetail.tsx`
- Updated import: `@/services/controller/ZoneController` → `@/services/slave/sgw/ZoneController`
- Updated types: `API.Thing` → `SgwModel.SgwThing`
- Updated types: `API.UserResponse` → `MasterModel.UserResponse`
- Updated types: `API.Geom` → `SgwModel.Geom`
### Migration Changes
**Before:**
```typescript
import { apiGetZoneById } from '@/services/controller/ZoneController';
thing: API.Thing
const zone_geom: API.Geom = ...
```
**After:**
```typescript
import { apiGetZoneById } from '@/services/slave/sgw/ZoneController';
thing: SgwModel.SgwThing
const zone_geom: SgwModel.Geom = ...
```
### Type Definitions
```typescript
declare namespace SgwModel {
interface Banzone {
id?: string;
name?: string;
province_code?: string;
type?: number;
conditions?: Condition[];
description?: string;
geometry?: string;
enabled?: boolean;
created_at?: Date;
updated_at?: Date;
}
interface Condition {
max?: number;
min?: number;
type?: 'length_limit' | 'month_range' | 'date_range';
to?: number;
from?: number;
}
interface Geom {
geom_type?: number;
geom_poly?: string;
geom_lines?: string;
geom_point?: string;
geom_radius?: number;
}
interface ZoneResponse {
total?: number;
offset?: number;
limit?: number;
banzones?: Banzone[];
}
interface ZoneBodyRequest {
name?: string;
province_code?: string;
type?: number;
conditions?: Condition[];
description?: string;
geometry?: string;
enabled?: boolean;
}
}
```
### API Usage Examples
**Get Zone by ID:**
```typescript
import { apiGetZoneById } from '@/services/slave/sgw/ZoneController';
const zone = await apiGetZoneById('zone-123');
const geometry: SgwModel.Geom = JSON.parse(zone.geometry || '{}');
```
**Create Banzone:**
```typescript
import { apiCreateBanzone } from '@/services/slave/sgw/ZoneController';
const zoneData: SgwModel.ZoneBodyRequest = {
name: 'Vùng cấm mùa sinh sản',
province_code: 'QN',
type: 1,
enabled: true,
conditions: [
{
type: 'month_range',
from: 4,
to: 8,
},
],
geometry: JSON.stringify({
geom_type: 1,
geom_poly: 'POLYGON(...)',
}),
};
await apiCreateBanzone(zoneData);
```
**Get All Banzones:**
```typescript
import { apiGetAllBanzones } from '@/services/slave/sgw/ZoneController';
const response = await apiGetAllBanzones({
offset: 0,
limit: 20,
order: 'name',
dir: 'asc',
});
```
## Status: ✅ Complete
- ✅ 5 API functions migrated
- ✅ 5 type definitions added
- ✅ 2 route constants added
- ✅ 1 file updated (ShipDetail.tsx)
- ✅ 0 compilation errors
## Total SGW Migration Progress
| Module | APIs | Types | Status |
| --------- | ------ | ------- | ------ |
| Ship | 12 | 15+ | ✅ |
| Trip | 17 | 21+ | ✅ |
| Photo | 2 | 2 | ✅ |
| Zone | 5 | 5 | ✅ |
| **TOTAL** | **36** | **43+** | ✅ |
---
**Migration Date:** January 23, 2026
**Status:** ✅ Complete
**Ready for Testing:** YES

View File

@@ -25,8 +25,8 @@ import {
} from './utils/storage'; } from './utils/storage';
const isProdBuild = process.env.NODE_ENV === 'production'; const isProdBuild = process.env.NODE_ENV === 'production';
export type InitialStateResponse = { export type InitialStateResponse = {
getUserProfile?: () => Promise<MasterModel.ProfileResponse | undefined>; getUserProfile?: () => Promise<MasterModel.UserResponse | undefined>;
currentUserProfile?: MasterModel.ProfileResponse; currentUserProfile?: MasterModel.UserResponse;
theme?: 'light' | 'dark'; theme?: 'light' | 'dark';
}; };

View File

@@ -13,7 +13,7 @@ import { Dropdown } from 'antd';
export const AvatarDropdown = ({ export const AvatarDropdown = ({
currentUserProfile, currentUserProfile,
}: { }: {
currentUserProfile?: MasterModel.ProfileResponse; currentUserProfile?: MasterModel.UserResponse;
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
return ( return (

View File

@@ -6,10 +6,9 @@ import {
WarningOutlined, WarningOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Flex, Tag, Tooltip } from 'antd'; import { Flex, Tag, theme, Tooltip } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import { TagStateCallbackPayload } from '../../pages/Slave/SGW/Map/type'; import { TagStateCallbackPayload } from '../../pages/Slave/SGW/Map/type';
import style from './index.less';
type TagStateProps = { type TagStateProps = {
normalCount?: number; normalCount?: number;
@@ -35,7 +34,59 @@ const TagState = ({
sos: false, sos: false,
disconnected: false, disconnected: false,
}); });
const { token } = theme.useToken();
const intl = useIntl(); const intl = useIntl();
// Style variants using antd theme tokens for dark mode support
const getTagStyle = (
type: 'normal' | 'warning' | 'critical' | 'offline',
isActive: boolean,
) => {
const baseStyle = {
borderRadius: token.borderRadiusSM,
borderWidth: 1,
borderStyle: 'solid' as const,
};
if (type === 'normal') {
return {
...baseStyle,
color: isActive ? token.colorSuccess : token.colorSuccess,
backgroundColor: isActive
? token.colorSuccessBg
: token.colorBgContainer,
borderColor: token.colorSuccessBorder,
};
}
if (type === 'warning') {
return {
...baseStyle,
color: isActive ? token.colorWarning : token.colorWarning,
backgroundColor: isActive
? token.colorWarningBg
: token.colorBgContainer,
borderColor: token.colorWarningBorder,
};
}
if (type === 'critical') {
return {
...baseStyle,
color: isActive ? token.colorError : token.colorError,
backgroundColor: isActive ? token.colorErrorBg : token.colorBgContainer,
borderColor: token.colorErrorBorder,
};
}
// offline
return {
...baseStyle,
color: token.colorTextSecondary,
backgroundColor: isActive
? token.colorFillSecondary
: token.colorBgContainer,
borderColor: token.colorBorder,
};
};
const handleTagClick = (key: keyof typeof activeStates) => { const handleTagClick = (key: keyof typeof activeStates) => {
const newStates = { ...activeStates, [key]: !activeStates[key] }; const newStates = { ...activeStates, [key]: !activeStates[key] };
setActiveStates(newStates); setActiveStates(newStates);
@@ -45,39 +96,11 @@ const TagState = ({
isWarning: newStates.warning, isWarning: newStates.warning,
isCritical: newStates.critical, isCritical: newStates.critical,
isSos: newStates.sos, isSos: newStates.sos,
isDisconected: newStates.disconnected, isDisconnected: newStates.disconnected,
}); });
} }
}; };
// const tagStyles = {
// sos: activeStates.sos
// ? { background: '#ff4d4f', color: '#fff', borderColor: '#ff4d4f' }
// : { background: '#fff1f0', color: '#cf1322', borderColor: '#ffa39e' },
// normal: activeStates.normal
// ? { background: '#52c41a', color: '#fff', borderColor: '#52c41a' }
// : { background: '#f6ffed', color: '#389e0d', borderColor: '#b7eb8f' },
// warning: activeStates.warning
// ? {
// background: '#faad14',
// color: '#fff',
// borderColor: '#faad14',
// hoverColor: '#ffe58f',
// }
// : {
// background: '#fffbe6',
// color: '#d48806',
// borderColor: '#ffe58f',
// hoverColor: '#faad14',
// },
// critical: activeStates.critical
// ? { background: '#d4380d', color: '#fff', borderColor: '#d4380d' }
// : { background: '#fff2e8', color: '#d4380d', borderColor: '#ffd8bf' },
// disconnected: activeStates.disconnected
// ? { background: '#8c8c8c', color: '#fff', borderColor: '#8c8c8c' }
// : { background: '#f5f5f5', color: '#8c8c8c', borderColor: '#d9d9d9' },
// };
return ( return (
<Flex <Flex
gap={1} gap={1}
@@ -94,13 +117,12 @@ const TagState = ({
{sosCount !== undefined && ( {sosCount !== undefined && (
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id: 'thing.status.sos', id: 'common.level.sos',
defaultMessage: 'SOS', defaultMessage: 'SOS',
})} })}
> >
<Tag.CheckableTag <Tag.CheckableTag
className={activeStates.sos ? style.criticalActive : style.critical} style={getTagStyle('critical', activeStates.sos)}
// style={tagStyles.sos}
icon={<AlertOutlined />} icon={<AlertOutlined />}
checked={activeStates.sos} checked={activeStates.sos}
onChange={() => handleTagClick('sos')} onChange={() => handleTagClick('sos')}
@@ -113,13 +135,12 @@ const TagState = ({
{/* {normalCount > 0 && ( */} {/* {normalCount > 0 && ( */}
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id: 'thing.status.normal', id: 'common.level.normal',
defaultMessage: 'Normal', defaultMessage: 'Normal',
})} })}
> >
<Tag.CheckableTag <Tag.CheckableTag
className={activeStates.normal ? style.normalActive : style.normal} style={getTagStyle('normal', activeStates.normal)}
// style={tagStyles.normal}
icon={<CheckOutlined />} icon={<CheckOutlined />}
checked={activeStates.normal} checked={activeStates.normal}
onChange={() => handleTagClick('normal')} onChange={() => handleTagClick('normal')}
@@ -131,14 +152,13 @@ const TagState = ({
{/* {warningCount > 0 && ( */} {/* {warningCount > 0 && ( */}
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id: 'thing.status.warning', id: 'common.level.warning',
defaultMessage: 'Warning', defaultMessage: 'Warning',
})} })}
> >
<Tag.CheckableTag <Tag.CheckableTag
className={activeStates.warning ? style.warningActive : style.warning} style={getTagStyle('warning', activeStates.warning)}
icon={<WarningOutlined />} icon={<WarningOutlined />}
// style={tagStyles.warning}
checked={activeStates.warning} checked={activeStates.warning}
onChange={() => handleTagClick('warning')} onChange={() => handleTagClick('warning')}
> >
@@ -149,16 +169,13 @@ const TagState = ({
{/* {criticalCount > 0 && ( */} {/* {criticalCount > 0 && ( */}
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id: 'thing.status.critical', id: 'common.level.critical',
defaultMessage: 'Critical', defaultMessage: 'Critical',
})} })}
> >
<Tag.CheckableTag <Tag.CheckableTag
className={ style={getTagStyle('critical', activeStates.critical)}
activeStates.critical ? style.criticalActive : style.critical
}
icon={<ExclamationOutlined />} icon={<ExclamationOutlined />}
// style={tagStyles.critical}
checked={activeStates.critical} checked={activeStates.critical}
onChange={() => handleTagClick('critical')} onChange={() => handleTagClick('critical')}
> >
@@ -169,16 +186,13 @@ const TagState = ({
{/* {disconnectedCount > 0 && ( */} {/* {disconnectedCount > 0 && ( */}
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id: 'thing.status.disconnected', id: 'common.level.disconnected',
defaultMessage: 'Disconnected', defaultMessage: 'Disconnected',
})} })}
> >
<Tag.CheckableTag <Tag.CheckableTag
className={ style={getTagStyle('offline', activeStates.disconnected)}
activeStates.disconnected ? style.offlineActive : style.offline
}
icon={<DisconnectOutlined />} icon={<DisconnectOutlined />}
// style={tagStyles.disconnected}
checked={activeStates.disconnected} checked={activeStates.disconnected}
onChange={() => handleTagClick('disconnected')} onChange={() => handleTagClick('disconnected')}
> >

View File

@@ -5,18 +5,28 @@ import {
STATUS_WARNING, STATUS_WARNING,
} from '@/constants'; } from '@/constants';
import { Badge } from 'antd'; import { Badge } from 'antd';
import IconFont from '../IconFont';
export const getBadgeStatus = (status: number) => { export const getBadgeStatus = (status: number) => {
switch (status) { switch (status) {
case STATUS_NORMAL: case STATUS_NORMAL:
return <Badge status="success" />; return <Badge size="default" status="success" />;
case STATUS_WARNING: case STATUS_WARNING:
return <Badge status="warning" />; return <Badge size="default" status="warning" />;
case STATUS_DANGEROUS: case STATUS_DANGEROUS:
return <Badge status="error" />; return <Badge size="default" status="error" />;
case STATUS_SOS: case STATUS_SOS:
return <Badge status="error" />; return <Badge size="default" status="error" />;
default: default:
return <Badge status="default" />; return <Badge size="default" status="default" />;
}
};
export const getBadgeConnection = (online: boolean) => {
switch (online) {
case true:
return <Badge status="processing" />;
default:
return <IconFont type="icon-cloud-disconnect" />;
} }
}; };

View File

@@ -38,6 +38,7 @@ export default {
'common.type': 'Type', 'common.type': 'Type',
'common.type.placeholder': 'Select Type', 'common.type.placeholder': 'Select Type',
'common.status': 'Status', 'common.status': 'Status',
'common.connect': 'Connection',
'common.province': 'Province', 'common.province': 'Province',
'common.description': 'Description', 'common.description': 'Description',
'common.description.required': 'Description is required', 'common.description.required': 'Description is required',
@@ -48,6 +49,7 @@ export default {
'common.updated_at': 'Updated At', 'common.updated_at': 'Updated At',
'common.undefined': 'Undefined', 'common.undefined': 'Undefined',
'common.not_empty': 'Cannot be empty!', 'common.not_empty': 'Cannot be empty!',
'common.level.disconnected': 'Disconnected',
'common.level.normal': 'Normal', 'common.level.normal': 'Normal',
'common.level.warning': 'Warning', 'common.level.warning': 'Warning',
'common.level.critical': 'Critical', 'common.level.critical': 'Critical',

View File

@@ -39,6 +39,7 @@ export default {
'common.type': 'Loại', 'common.type': 'Loại',
'common.type.placeholder': 'Chọn loại', 'common.type.placeholder': 'Chọn loại',
'common.status': 'Trạng thái', 'common.status': 'Trạng thái',
'common.connect': 'Kết nối',
'common.province': 'Tỉnh', 'common.province': 'Tỉnh',
'common.description': 'Mô tả', 'common.description': 'Mô tả',
'common.description.required': 'Mô tả không được để trống', 'common.description.required': 'Mô tả không được để trống',
@@ -49,6 +50,7 @@ export default {
'common.updated_at': 'Ngày cập nhật', 'common.updated_at': 'Ngày cập nhật',
'common.undefined': 'Chưa xác định', 'common.undefined': 'Chưa xác định',
'common.not_empty': 'Không được để trống!', 'common.not_empty': 'Không được để trống!',
'common.level.disconnected': 'Mất kết nối',
'common.level.normal': 'Bình thường', 'common.level.normal': 'Bình thường',
'common.level.warning': 'Cảnh báo', 'common.level.warning': 'Cảnh báo',
'common.level.critical': 'Nguy hiểm', 'common.level.critical': 'Nguy hiểm',

View File

@@ -255,7 +255,7 @@ const ManagerDevicePage = () => {
const offset = current === 1 ? 0 : (current - 1) * size; const offset = current === 1 ? 0 : (current - 1) * size;
setIsLoading(true); setIsLoading(true);
const metadata: Partial<MasterModel.ThingMetadata> = {}; const metadata: Partial<MasterModel.SearchThingMetadata> = {};
if (external_id) metadata.external_id = external_id; if (external_id) metadata.external_id = external_id;
// Add group filter if groups are selected // Add group filter if groups are selected

View File

@@ -14,7 +14,7 @@ const SystemLogs = () => {
const tableRef = useRef<ActionType>(); const tableRef = useRef<ActionType>();
const { token } = theme.useToken(); const { token } = theme.useToken();
const queryUserSource = async (): Promise<MasterModel.ProfileResponse[]> => { const queryUserSource = async (): Promise<MasterModel.UserResponse[]> => {
try { try {
const body: MasterModel.SearchUserPaginationBody = { const body: MasterModel.SearchUserPaginationBody = {
offset: 0, offset: 0,

View File

@@ -14,7 +14,7 @@ enum AssignTabsKey {
const AssignUserPage = () => { const AssignUserPage = () => {
const { userId } = useParams<{ userId: string }>(); const { userId } = useParams<{ userId: string }>();
const [userProfile, setUserProfile] = const [userProfile, setUserProfile] =
useState<MasterModel.ProfileResponse | null>(null); useState<MasterModel.UserResponse | null>(null);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [tabSelected, setTabSelected] = useState<AssignTabsKey>( const [tabSelected, setTabSelected] = useState<AssignTabsKey>(
AssignTabsKey.group, AssignTabsKey.group,

View File

@@ -24,7 +24,7 @@ import {
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
const { Text } = Typography; const { Text } = Typography;
type AssignGroupProps = { type AssignGroupProps = {
user: MasterModel.ProfileResponse | null; user: MasterModel.UserResponse | null;
}; };
const AssignGroup = ({ user }: AssignGroupProps) => { const AssignGroup = ({ user }: AssignGroupProps) => {
const groupActionRef = useRef<ActionType>(); const groupActionRef = useRef<ActionType>();

View File

@@ -32,7 +32,7 @@ type PolicyShareDefault = {
}; };
type ShareThingProps = { type ShareThingProps = {
user: MasterModel.ProfileResponse | null; user: MasterModel.UserResponse | null;
}; };
const ShareThing = ({ user }: ShareThingProps) => { const ShareThing = ({ user }: ShareThingProps) => {
const listActionRef = useRef<ActionType>(); const listActionRef = useRef<ActionType>();

View File

@@ -34,19 +34,19 @@ const ManagerUserPage = () => {
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const [selectedRowsState, setSelectedRowsState] = useState< const [selectedRowsState, setSelectedRowsState] = useState<
MasterModel.ProfileResponse[] MasterModel.UserResponse[]
>([]); >([]);
const [groupCheckedKeys, setGroupCheckedKeys] = useState< const [groupCheckedKeys, setGroupCheckedKeys] = useState<
string | string[] | null string | string[] | null
>(null); >(null);
const handleClickAssign = (user: MasterModel.ProfileResponse) => { const handleClickAssign = (user: MasterModel.UserResponse) => {
const path = `${ROUTE_MANAGER_USERS}/${user.id}/${ROUTE_MANAGER_USERS_PERMISSIONS}`; const path = `${ROUTE_MANAGER_USERS}/${user.id}/${ROUTE_MANAGER_USERS_PERMISSIONS}`;
history.push(path); history.push(path);
}; };
const columns: ProColumns<MasterModel.ProfileResponse>[] = [ const columns: ProColumns<MasterModel.UserResponse>[] = [
{ {
key: 'email', key: 'email',
title: ( title: (
@@ -136,7 +136,7 @@ const ManagerUserPage = () => {
}, },
]; ];
const handleRemove = async (selectedRows: MasterModel.ProfileResponse[]) => { const handleRemove = async (selectedRows: MasterModel.UserResponse[]) => {
const key = 'remove_user'; const key = 'remove_user';
if (!selectedRows) return true; if (!selectedRows) return true;
@@ -151,7 +151,7 @@ const ManagerUserPage = () => {
key, key,
}); });
const allDelete = selectedRows.map( const allDelete = selectedRows.map(
async (row: MasterModel.ProfileResponse) => { async (row: MasterModel.UserResponse) => {
await apiDeleteUser(row?.id || ''); await apiDeleteUser(row?.id || '');
}, },
); );
@@ -196,7 +196,7 @@ const ManagerUserPage = () => {
/> />
</ProCard> </ProCard>
<ProCard colSpan={{ xs: 24, sm: 24, md: 18, lg: 18, xl: 18 }}> <ProCard colSpan={{ xs: 24, sm: 24, md: 18, lg: 18, xl: 18 }}>
<ProTable<MasterModel.ProfileResponse> <ProTable<MasterModel.UserResponse>
columns={columns} columns={columns}
tableLayout="auto" tableLayout="auto"
actionRef={actionRef} actionRef={actionRef}
@@ -210,7 +210,7 @@ const ManagerUserPage = () => {
selectedRowKeys: selectedRowsState.map((row) => row.id!), selectedRowKeys: selectedRowsState.map((row) => row.id!),
onChange: ( onChange: (
_: React.Key[], _: React.Key[],
selectedRows: MasterModel.ProfileResponse[], selectedRows: MasterModel.UserResponse[],
) => { ) => {
setSelectedRowsState(selectedRows); setSelectedRowsState(selectedRows);
}, },
@@ -249,12 +249,12 @@ const ManagerUserPage = () => {
let users = userByGroupResponses.users || []; let users = userByGroupResponses.users || [];
// Apply filters // Apply filters
if (email) { if (email) {
users = users.filter((user: MasterModel.ProfileResponse) => users = users.filter((user: MasterModel.UserResponse) =>
user.email?.includes(email), user.email?.includes(email),
); );
} }
if (phone_number) { if (phone_number) {
users = users.filter((user: MasterModel.ProfileResponse) => users = users.filter((user: MasterModel.UserResponse) =>
user.metadata?.phone_number?.includes(phone_number), user.metadata?.phone_number?.includes(phone_number),
); );
} }
@@ -269,7 +269,7 @@ const ManagerUserPage = () => {
}; };
} else { } else {
// Use regular queryUsers API // Use regular queryUsers API
const metadata: Partial<MasterModel.ProfileMetadata> = {}; const metadata: Partial<MasterModel.UserMetadata> = {};
if (phone_number) metadata.phone_number = phone_number; if (phone_number) metadata.phone_number = phone_number;
const query: MasterModel.SearchUserPaginationBody = { const query: MasterModel.SearchUserPaginationBody = {

View File

@@ -32,7 +32,7 @@ const ChangeProfile = () => {
}} }}
onFinish={async (values) => { onFinish={async (values) => {
try { try {
const body: Partial<MasterModel.ProfileMetadata> = { const body: Partial<MasterModel.UserMetadata> = {
full_name: values.full_name, full_name: values.full_name,
phone_number: values.phone_number, phone_number: values.phone_number,
}; };

View File

@@ -61,7 +61,7 @@ const ShipDetail = ({
let shipOwner: let shipOwner:
| MasterModel.UserResponse | MasterModel.UserResponse
| MasterModel.ProfileResponse | MasterModel.UserResponse
| null = null; | null = null;
if (resp?.owner_id) { if (resp?.owner_id) {
if (initialState?.currentUserProfile?.metadata?.user_type === 'admin') { if (initialState?.currentUserProfile?.metadata?.user_type === 'admin') {

View File

@@ -206,7 +206,7 @@ const MapPage = () => {
? '' ? ''
: [stateCriticalQuery, stateSosQuery].filter(Boolean).join(','); : [stateCriticalQuery, stateSosQuery].filter(Boolean).join(',');
let metaFormQuery: Record<string, any> = {}; let metaFormQuery: Record<string, any> = {};
if (stateQuery?.isDisconected) if (stateQuery?.isDisconnected)
metaFormQuery = { ...metaFormQuery, connected: false }; metaFormQuery = { ...metaFormQuery, connected: false };
const metaStateQuery = const metaStateQuery =
stateQueryParams !== '' ? { state_level: stateQueryParams } : null; stateQueryParams !== '' ? { state_level: stateQueryParams } : null;
@@ -252,7 +252,7 @@ const MapPage = () => {
}; };
setThings(null); setThings(null);
const resp = await apiSearchThings(metaQuery); const resp = await apiSearchThings(metaQuery, 'sgw');
return resp; return resp;
} catch (error) { } catch (error) {
console.error('Error when searchThings: ', error); console.error('Error when searchThings: ', error);

View File

@@ -58,7 +58,7 @@ export type TagStateCallbackPayload = {
isWarning: boolean; isWarning: boolean;
isCritical: boolean; isCritical: boolean;
isSos: 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 = { export type PointData = {
@@ -82,7 +82,7 @@ export interface GPSParseResult {
export interface ShipDetailData { export interface ShipDetailData {
ship: SgwModel.ShipDetail; ship: SgwModel.ShipDetail;
owner: MasterModel.ProfileResponse | null; owner: MasterModel.UserResponse | null;
gps: GPSParseResult | null; gps: GPSParseResult | null;
trip_id?: string; trip_id?: string;
zone_entered_alarm_list: ZoneAlarmParse[]; zone_entered_alarm_list: ZoneAlarmParse[];

View File

@@ -13,7 +13,7 @@ interface EditModalProps {
onVisibleChange: (visible: boolean) => void; onVisibleChange: (visible: boolean) => void;
onFinish: ( onFinish: (
values: Pick<MasterModel.Thing, 'name'> & values: Pick<MasterModel.Thing, 'name'> &
Pick<MasterModel.ThingMetadata, 'address' | 'external_id'>, Pick<MasterModel.ThingReponseMetadata, 'address' | 'external_id'>,
) => Promise<boolean>; ) => Promise<boolean>;
} }

View File

@@ -31,6 +31,18 @@ interface CreateTripProps {
thing_id?: string; 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 CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [selectedThing, setSelectedThing] = useState<string | undefined>(); const [selectedThing, setSelectedThing] = useState<string | undefined>();
@@ -41,9 +53,9 @@ const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [lastTrip, setLastTrip] = useState<SgwModel.Trip | null>(null); const [lastTrip, setLastTrip] = useState<SgwModel.Trip | null>(null);
const [initialFormValues, setInitialFormValues] = useState< 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( const { homeports, getHomeportsByProvinceCode } = useModel(
'slave.sgw.useHomePorts', '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 { try {
if (!selectedShip) { if (!selectedShip) {
message.error('Vui lòng chọn tàu!'); 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)) .filter((n: number) => !isNaN(n))
: [], : [],
fishing_gears: values.fishing_gear, fishing_gears: values.fishing_gear,
trip_cost: values.trip_cost, trip_cost: values.trip_cost || [],
}; };
const thingIdToUse = selectedShip.thing_id || selectedThing; const thingIdToUse = selectedShip.thing_id || selectedThing;
@@ -211,7 +223,7 @@ const CreateTrip: React.FC<CreateTripProps> = ({ onSuccess, thing_id }) => {
> >
<StepsForm<SgwModel.TripCreateParams> <StepsForm<SgwModel.TripCreateParams>
stepsProps={{ size: 'small' }} stepsProps={{ size: 'small' }}
onFinish={handleSubmit} onFinish={(values) => handleSubmit(values as TripFormValues)}
submitter={{ submitter={{
render: (_: SubmitterProps, dom) => dom, render: (_: SubmitterProps, dom) => dom,
}} }}

View File

@@ -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 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 ( return (
<div> <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> </div>
); );
}; };

View File

@@ -16,13 +16,13 @@ export async function apiLogin(body: MasterModel.LoginRequestBody) {
} }
export async function apiQueryProfile() { export async function apiQueryProfile() {
return request<MasterModel.ProfileResponse>(API_PATH_GET_PROFILE); return request<MasterModel.UserResponse>(API_PATH_GET_PROFILE);
} }
export async function apiUpdateProfile( export async function apiUpdateProfile(
body: Partial<MasterModel.ProfileMetadata>, body: Partial<MasterModel.UserMetadata>,
) { ) {
return request<MasterModel.ProfileResponse>(API_USERS, { return request<MasterModel.UserResponse>(API_USERS, {
method: 'PUT', method: 'PUT',
data: { data: {
metadata: body, metadata: body,

View File

@@ -5,6 +5,19 @@ import {
} from '@/constants/api'; } from '@/constants/api';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
export async function apiSearchThings(
body: MasterModel.SearchPaginationBody,
domain: 'spole',
): Promise<SpoleModel.SpoleThingsResponse>;
export async function apiSearchThings(
body: MasterModel.SearchPaginationBody,
domain: 'sgw',
): Promise<SgwModel.SgwThingsResponse>;
export async function apiSearchThings(
body: MasterModel.SearchPaginationBody,
domain?: 'gms',
): Promise<GmsModel.GmsThingsResponse>;
export async function apiSearchThings( export async function apiSearchThings(
body: MasterModel.SearchPaginationBody, body: MasterModel.SearchPaginationBody,
domain: string = process.env.DOMAIN_ENV || 'gms', domain: string = process.env.DOMAIN_ENV || 'gms',

View File

@@ -4,19 +4,21 @@ import { request } from '@umijs/max';
export async function apiQueryUsers( export async function apiQueryUsers(
params: MasterModel.SearchUserPaginationBody, params: MasterModel.SearchUserPaginationBody,
) { ) {
return request<MasterModel.UserResponse>(API_USERS, { return request<MasterModel.UserListResponse>(API_USERS, {
params: params, params: params,
}); });
} }
export async function apiQueryUserById(userId: string) { export async function apiQueryUserById(userId: string) {
return request<MasterModel.ProfileResponse>(`${API_USERS}/${userId}`); return request<MasterModel.UserResponse>(`${API_USERS}/${userId}`);
} }
export async function apiQueryUsersByGroup( export async function apiQueryUsersByGroup(
group_id: string, group_id: string,
): Promise<MasterModel.UserResponse> { ): Promise<MasterModel.UserListResponse> {
return request<MasterModel.UserResponse>(`${API_USERS_BY_GROUP}/${group_id}`); return request<MasterModel.UserListResponse>(
`${API_USERS_BY_GROUP}/${group_id}`,
);
} }
export async function apiCreateUsers(body: MasterModel.CreateUserBodyRequest) { export async function apiCreateUsers(body: MasterModel.CreateUserBodyRequest) {

View File

@@ -8,254 +8,12 @@ declare namespace MasterModel {
limit?: number; limit?: number;
dir?: 'asc' | 'desc'; dir?: 'asc' | 'desc';
} }
interface SearchThingPaginationBody extends SearchPaginationBody {
order?: string;
metadata?: ThingMetadata;
}
interface ThingMetadata {
group_id?: string;
external_id?: string;
}
interface SearchAlarmPaginationBody extends SearchPaginationBody { interface PaginationReponse {
order?: 'name' | undefined;
thing_name?: string;
thing_id?: string;
level?: number;
}
interface SearchUserPaginationBody extends SearchPaginationBody {
order?: 'email' | 'name' | undefined;
email?: string;
metadata?: Partial<ProfileMetadata>;
}
interface SearchLogPaginationBody extends SearchPaginationBody {
from?: number;
to?: number;
publisher?: string;
subtopic?: string;
}
// Auth
interface LoginRequestBody {
guid: string;
email: string;
password: string;
}
interface LoginResponse {
token?: string;
}
interface ChangePasswordRequestBody {
old_password: string;
password: string;
}
interface ProfileResponse {
id?: string;
email?: string;
metadata?: ProfileMetadata;
}
interface ProfileMetadata {
frontend_thing_id?: string;
frontend_thing_key?: string;
full_name?: string;
phone_number?: string;
telegram?: string;
user_type?: 'admin' | 'enduser' | 'sysadmin' | 'users';
}
// User
interface CreateUserMetadata extends ProfileMetadata {
group_id?: string;
}
interface CreateUserBodyRequest extends Partial<ProfileResponse> {
password: string;
full_name?: string;
metadata?: CreateUserMetadata;
}
interface UserResponse {
total?: number;
offset?: number;
limit?: number;
users: ProfileResponse[];
}
interface AlarmsResponse {
total?: number;
limit?: number;
order?: string;
dir?: string;
alarms?: Alarm[];
}
interface ConfirmAlarmRequest {
id?: string;
description?: string;
thing_id?: string;
time?: number;
}
// Alarm
interface Alarm {
name?: string;
time?: number;
level?: number;
id?: string;
confirmed?: boolean;
confirmed_email?: string;
confirmed_time?: number;
confirmed_desc?: string;
thing_id?: string;
thing_name?: string;
thing_type?: string;
}
// Thing
interface ThingMetadata {
address?: string;
alarm_list?: string;
cfg_channel_id?: string;
connected?: boolean;
ctrl_channel_id?: string;
data_channel_id?: string;
enduser?: string;
external_id?: string;
group_id?: string;
req_channel_id?: string;
state?: string;
state_level?: number;
state_updated_time?: number;
type?: string;
updated_time?: number;
}
interface ThingsResponse<T extends ThingMetadata = ThingMetadata> {
total?: number; total?: number;
offset?: number; offset?: number;
limit?: number; limit?: number;
order?: string; order?: string;
direction?: string; direction?: string;
metadata?: ThingsResponseMetadata;
things?: Thing<T>[];
}
interface ThingsResponseMetadata {
total_connected?: number;
total_filter?: number;
total_sos?: number;
total_state_level_0?: number;
total_state_level_1?: number;
total_state_level_2?: number;
total_thing?: number;
}
interface Thing<T extends ThingMetadata = ThingMetadata> {
id?: string;
name?: string;
key?: string;
metadata?: T;
}
// Thing Policy
interface ThingPolicyResponse {
total?: number;
offset?: number;
limit?: number;
order?: string;
direction?: string;
metadata?: null;
things?: ThingPolicy[];
}
interface ThingPolicy {
policies?: Policy[];
thing_id?: string;
thing_name?: string;
external_id?: string;
}
type Policy = 'read' | 'delete' | 'write';
// Group
interface GroupBodyRequest {
id?: string;
name: string;
parent_id?: string;
description?: string;
metadata?: GroupMetadata;
}
interface AddGroupBodyResponse extends Partial<GroupBodyRequest> {
id: string;
}
interface GroupMetadata {
code?: string;
short_name?: string;
has_thing?: boolean;
[key: string]: unknown;
}
interface GroupResponse {
total?: number;
level?: number;
name?: string;
groups?: GroupNode[];
}
interface GroupNode {
id: string;
name: string;
owner_id: string;
description: string;
metadata: GroupMetadata;
level: number;
path: string;
parent_id?: string;
created_at?: string;
updated_at?: string;
children?: GroupNode[]; // Đệ quy: mỗi node có thể có children là mảng GroupNode
[key: string]: any; // Nếu có thêm trường động
}
interface GroupQueryParams {
level?: number;
tree?: boolean;
[key: string]: any;
}
// Log
type LogTypeRequest = 'user_logs' | undefined;
interface LogResponse {
offset?: number;
limit?: number;
publisher?: string;
from?: number;
to?: number;
format?: string;
total?: number;
messages?: Message[];
}
interface Message {
channel?: string;
subtopic?: string;
publisher?: string;
protocol?: string;
name?: string;
time?: number;
string_value?: string;
}
// User
interface AssignMemberRequest {
group_id: string;
type: 'users' | 'admin' | 'things' | undefined;
members: string[];
} }
} }

36
src/services/master/typings/alarm.d.ts vendored Normal file
View File

@@ -0,0 +1,36 @@
declare namespace MasterModel {
interface SearchAlarmPaginationBody extends SearchPaginationBody {
order?: 'name' | undefined;
thing_name?: string;
thing_id?: string;
level?: number;
}
interface AlarmsResponse {
total?: number;
limit?: number;
order?: string;
dir?: string;
alarms?: Alarm[];
}
interface ConfirmAlarmRequest {
id?: string;
description?: string;
thing_id?: string;
time?: number;
}
// Alarm
interface Alarm {
name?: string;
time?: number;
level?: number;
id?: string;
confirmed?: boolean;
confirmed_email?: string;
confirmed_time?: number;
confirmed_desc?: string;
thing_id?: string;
thing_name?: string;
thing_type?: string;
}
}

11
src/services/master/typings/auth.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
declare namespace MasterModel {
interface LoginRequestBody {
guid: string;
email: string;
password: string;
}
interface LoginResponse {
token?: string;
}
}

52
src/services/master/typings/group.d.ts vendored Normal file
View File

@@ -0,0 +1,52 @@
declare namespace MasterModel {
interface GroupBodyRequest {
id?: string;
name: string;
parent_id?: string;
description?: string;
metadata?: GroupMetadata;
}
interface AddGroupBodyResponse extends Partial<GroupBodyRequest> {
id: string;
}
interface GroupMetadata {
code?: string;
short_name?: string;
has_thing?: boolean;
[key: string]: unknown;
}
interface GroupResponse {
total?: number;
level?: number;
name?: string;
groups?: GroupNode[];
}
interface GroupNode {
id: string;
name: string;
owner_id: string;
description: string;
metadata: GroupMetadata;
level: number;
path: string;
parent_id?: string;
created_at?: string;
updated_at?: string;
children?: GroupNode[]; // Đệ quy: mỗi node có thể có children là mảng GroupNode
[key: string]: any; // Nếu có thêm trường động
}
interface GroupQueryParams {
level?: number;
tree?: boolean;
[key: string]: any;
}
interface AssignMemberRequest {
group_id: string;
type: 'users' | 'admin' | 'things' | undefined;
members: string[];
}
}

31
src/services/master/typings/log.d.ts vendored Normal file
View File

@@ -0,0 +1,31 @@
declare namespace MasterModel {
interface SearchLogPaginationBody extends SearchPaginationBody {
from?: number;
to?: number;
publisher?: string;
subtopic?: string;
}
type LogTypeRequest = 'user_logs' | undefined;
interface LogResponse {
offset?: number;
limit?: number;
publisher?: string;
from?: number;
to?: number;
format?: string;
total?: number;
messages?: Message[];
}
interface Message {
channel?: string;
subtopic?: string;
publisher?: string;
protocol?: string;
name?: string;
time?: number;
string_value?: string;
}
}

73
src/services/master/typings/thing.d.ts vendored Normal file
View File

@@ -0,0 +1,73 @@
declare namespace MasterModel {
interface SearchThingPaginationBody<T = SearchThingMetadata>
extends SearchPaginationBody {
order?: string;
metadata?: T;
}
interface SearchThingMetadata {
external_id?: string;
state_level?: string; // vd: "normal,warning,critical,sos"
/** kết nối */
connected?: boolean;
group_id?: string; // "id1,id2,id3"
}
// Thing
interface ThingReponseMetadata {
address?: string;
alarm_list?: string;
cfg_channel_id?: string;
connected?: boolean;
ctrl_channel_id?: string;
data_channel_id?: string;
enduser?: string;
external_id?: string;
group_id?: string;
req_channel_id?: string;
state?: string;
state_level?: number;
state_updated_time?: number;
type?: string;
updated_time?: number;
}
interface ThingsResponse<
T extends ThingReponseMetadata = ThingReponseMetadata,
> extends PaginationReponse {
metadata?: ThingsResponseMetadata;
things?: Thing<T>[];
}
interface ThingsResponseMetadata {
total_connected?: number;
total_filter?: number;
total_sos?: number;
total_state_level_0?: number;
total_state_level_1?: number;
total_state_level_2?: number;
total_thing?: number;
}
interface Thing<T extends ThingReponseMetadata = ThingReponseMetadata> {
id?: string;
name?: string;
key?: string;
metadata?: T;
}
// Thing Policy
interface ThingPolicyResponse extends PaginationReponse {
metadata?: null;
things?: ThingPolicy[];
}
interface ThingPolicy {
policies?: Policy[];
thing_id?: string;
thing_name?: string;
external_id?: string;
}
type Policy = 'read' | 'delete' | 'write';
}

44
src/services/master/typings/user.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
declare namespace MasterModel {
interface SearchUserPaginationBody extends SearchPaginationBody {
order?: 'email' | 'name' | undefined;
email?: string;
metadata?: Partial<UserMetadata>;
}
interface ChangePasswordRequestBody {
old_password: string;
password: string;
}
interface UserResponse {
id?: string;
email?: string;
metadata?: UserMetadata;
}
interface UserMetadata {
frontend_thing_id?: string;
frontend_thing_key?: string;
full_name?: string;
phone_number?: string;
telegram?: string;
user_type?: 'admin' | 'enduser' | 'sysadmin' | 'users';
}
// User
interface CreateUserMetadata extends UserMetadata {
group_id?: string;
}
interface CreateUserBodyRequest extends Partial<UserResponse> {
password: string;
full_name?: string;
metadata?: CreateUserMetadata;
}
interface UserListResponse {
total?: number;
offset?: number;
limit?: number;
users: UserResponse[];
}
}

View File

@@ -42,7 +42,7 @@ export async function apiGetZoneById(
* Create a new banzone * Create a new banzone
* @param body Banzone data * @param body Banzone data
*/ */
export async function apiCreateBanzone(body: SgwModel.ZoneBodyRequest) { export async function apiCreateBanzone(body: SgwModel.ZoneBasicInfo) {
return request(SGW_ROUTE_BANZONES, { return request(SGW_ROUTE_BANZONES, {
method: 'POST', method: 'POST',
data: body, data: body,
@@ -56,7 +56,7 @@ export async function apiCreateBanzone(body: SgwModel.ZoneBodyRequest) {
*/ */
export async function apiUpdateBanzone( export async function apiUpdateBanzone(
id: string, id: string,
body: SgwModel.ZoneBodyRequest, body: SgwModel.ZoneBasicInfo,
) { ) {
return request(`${SGW_ROUTE_BANZONES}/${id}`, { return request(`${SGW_ROUTE_BANZONES}/${id}`, {
method: 'PUT', method: 'PUT',

View File

@@ -3,7 +3,7 @@
declare namespace SgwModel { declare namespace SgwModel {
// Thing // Thing
interface ThingMedata extends MasterModel.ThingMetadata { interface ThingMedata extends MasterModel.ThingReponseMetadata {
gps?: string; gps?: string;
gps_time?: string; gps_time?: string;
ship_group_id?: string; ship_group_id?: string;
@@ -23,442 +23,4 @@ declare namespace SgwModel {
type SgwThingsResponse = MasterModel.ThingsResponse<SgwModel.ThingMedata>; type SgwThingsResponse = MasterModel.ThingsResponse<SgwModel.ThingMedata>;
type SgwThing = MasterModel.Thing<SgwModel.ThingMedata>; type SgwThing = MasterModel.Thing<SgwModel.ThingMedata>;
// Ship
interface ShipMetadata {
crew_count?: number;
home_port?: string;
home_port_point?: string;
ship_type?: string;
trip_arrival_port?: string;
trip_arrival_port_point?: string;
trip_arrival_time?: Date;
trip_depart_port?: string;
trip_depart_port_point?: string;
trip_departure_time?: Date;
trip_id?: string;
trip_name?: string;
trip_state?: number;
}
interface ShipType {
id?: number;
name?: string;
description?: string;
}
interface ShipDetail {
id?: string;
thing_id?: string;
owner_id?: string;
name?: string;
ship_type?: number;
home_port?: number;
ship_length?: number;
ship_power?: number;
reg_number?: string;
imo_number?: string;
mmsi_number?: string;
fishing_license_number?: string;
fishing_license_expiry_date?: Date | string;
province_code?: string;
ship_group_id?: string | null;
created_at?: Date | string;
updated_at?: Date | string;
metadata?: ShipMetadata;
}
interface ShipCreateParams {
thing_id?: string;
name?: string;
reg_number?: string;
ship_type?: number;
ship_length?: number;
ship_power?: number;
ship_group_id?: string;
home_port?: number;
fishing_license_number?: string;
fishing_license_expiry_date?: string;
}
interface ShipUpdateParams {
name?: string;
reg_number?: string;
ship_type?: number;
ship_group_id?: string | null;
ship_length?: number;
ship_power?: number;
home_port?: number;
fishing_license_number?: string;
fishing_license_expiry_date?: string;
metadata?: Record<string, unknown>;
}
interface ShipQueryParams {
offset?: number;
limit?: number;
order?: string;
dir?: 'asc' | 'desc';
name?: string;
registration_number?: string;
ship_type?: number;
ship_group_id?: string;
thing_id?: string;
}
interface ShipQueryResponse {
ships: ShipDetail[];
total: number;
offset: number;
limit: number;
}
interface ShipsResponse<T extends ShipMetadata = ShipMetadata> {
total?: number;
offset?: number;
limit?: number;
order?: string;
direction?: string;
Ship?: Ship<T>[];
}
// Ship Group
interface ShipGroup {
id: string;
name: string;
owner_id?: string;
description?: string;
created_at?: Date | string;
updated_at?: Date | string;
metadata?: Record<string, unknown>;
}
interface GroupShipResponse {
id?: string;
name?: string;
owner_id?: string;
description?: string;
}
interface ShipGroupCreateParams {
name: string;
description?: string;
metadata?: Record<string, unknown>;
}
interface ShipGroupUpdateParams {
name?: string;
description?: string;
metadata?: Record<string, unknown>;
}
// Port
interface Port {
id: number;
name: string;
type: string;
classification: string;
position_point: string;
has_origin_confirm: boolean;
province_code: string;
updated_at: string;
is_deleted: boolean;
}
interface PortQueryParams {
name?: string;
order?: string;
dir?: 'asc' | 'desc';
limit?: number;
offset?: number;
metadata?: {
province_code?: string;
};
}
interface PortQueryResponse {
total: number;
offset: number;
limit: number;
ports: Port[];
}
// Trip Management
interface FishingGear {
name: string;
number: string;
}
interface TripCost {
type: string;
unit: string;
amount: string;
total_cost: string;
cost_per_unit: string;
}
interface TripCrewPerson {
personal_id: string;
name: string;
phone: string;
email: string;
birth_date: Date;
note: string;
address: string;
created_at: Date;
updated_at: Date;
}
interface TripCrews {
role: string;
joined_at: Date;
left_at: Date | null;
note: string | null;
Person: TripCrewPerson;
}
interface CrewCreateParams {
personal_id: string;
name: string;
phone?: string;
email?: string;
birth_date?: string;
address?: string;
note?: string;
}
interface CrewUpdateParams {
name?: string;
phone?: string;
email?: string;
birth_date?: string;
address?: string;
note?: string;
}
interface TripCrewCreateParams {
trip_id: string;
personal_id: string;
role: string;
note?: string;
}
interface TripCrewUpdateParams {
trip_id: string;
personal_id: string;
role?: string;
note?: string;
}
interface TripCrewQueryResponse {
trip_crews: TripCrews[];
total?: number;
}
interface FishingLogInfo {
fish_species_id?: number;
fish_name?: string;
catch_number?: number;
catch_unit?: string;
fish_size?: number;
fish_rarity?: number;
fish_condition?: string;
gear_usage?: string;
}
interface FishingLog {
fishing_log_id?: string;
trip_id: string;
start_at: Date;
end_at: Date;
start_lat: number;
start_lon: number;
haul_lat: number;
haul_lon: number;
status: number;
weather_description: string;
info?: FishingLogInfo[];
sync: boolean;
}
interface NewFishingLogRequest {
trip_id: string;
start_at: Date;
start_lat: number;
start_lon: number;
weather_description: string;
}
interface FishSpecies {
id: number;
name: string;
scientific_name?: string;
description?: string;
}
interface FishSpeciesResponse extends FishSpecies {}
interface Trip {
id: string;
ship_id: string;
ship_length: number;
vms_id: string;
name: string;
fishing_gears: FishingGear[];
crews?: TripCrews[];
departure_time: string;
departure_port_id: number;
arrival_time: string;
arrival_port_id: number;
fishing_ground_codes: number[];
total_catch_weight: number | null;
total_species_caught: number | null;
trip_cost: TripCost[];
trip_status: number;
approved_by: string;
notes: string | null;
fishing_logs: FishingLog[] | null;
sync: boolean;
}
interface TripUpdateStateRequest {
status: number;
note?: string;
}
interface TripCreateParams {
name: string;
departure_time: string;
departure_port_id: number;
arrival_time?: string;
arrival_port_id?: number;
fishing_ground_codes?: number[];
fishing_gears?: FishingGear[];
crews?: TripCrews[];
trip_cost?: TripCost[];
notes?: 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?: FishingGear[];
trip_cost?: TripCost[];
ship_id?: string;
}
interface TripQueryParams {
name?: string;
order?: string;
dir?: 'asc' | 'desc';
offset?: number;
limit?: number;
metadata?: {
from?: string;
to?: string;
ship_name?: string;
reg_number?: string;
province_code?: string;
owner_id?: string;
ship_id?: string;
status?: string;
};
}
interface TripQueryResponse {
trips: Trip[];
total: number;
offset: number;
limit: number;
}
interface TripUpdateParams {
name?: string;
departure_time?: string;
departure_port_id?: number;
arrival_time?: string;
arrival_port_id?: number;
fishing_ground_codes?: number[];
fishing_gears?: FishingGear[];
crews?: TripCrews[];
trip_cost?: TripCost[];
trip_status?: number;
notes?: string;
}
interface TripDeleteParams {
trip_ids: string[];
}
// Photo Management
interface PhotoGetParams {
type: 'ship' | 'people';
id: string;
}
interface PhotoUploadParams {
type: 'ship' | 'people';
id: string;
file: File;
}
// Banzone Management
interface Banzone {
id?: string;
name?: string;
province_code?: string;
type?: number;
conditions?: Condition[];
description?: string;
geometry?: string;
enabled?: boolean;
created_at?: Date;
updated_at?: Date;
}
interface Condition {
max?: number;
min?: number;
type?: 'length_limit' | 'month_range' | 'date_range';
to?: number;
from?: number;
}
interface Geom {
geom_type?: number;
geom_poly?: string;
geom_lines?: string;
geom_point?: string;
geom_radius?: number;
}
interface ZoneResponse {
total?: number;
offset?: number;
limit?: number;
banzones?: Banzone[];
}
interface ZoneBodyRequest {
name?: string;
province_code?: string;
type?: number;
conditions?: Condition[];
description?: string;
geometry?: string;
enabled?: boolean;
}
}
declare namespace WsTypes {
interface WsThingResponse {
thing_id?: string;
key?: string;
data?: string;
time?: number;
}
} }

View File

@@ -0,0 +1,17 @@
declare namespace SgwModel {
interface CrewBaseInfo {
name: string;
phone?: string;
email?: string;
birth_date?: string;
address?: string;
note?: string;
}
interface CrewCreateParams extends CrewBaseInfo {
personal_id?: string;
}
interface CrewUpdateParams extends Partial<CrewBaseInfo> {
personal_id?: string;
}
}

View File

@@ -0,0 +1,8 @@
declare namespace SgwModel {
interface FishSpeciesResponse {
id: number;
name: string;
scientific_name?: string;
description?: string;
}
}

View File

@@ -0,0 +1,35 @@
declare namespace SgwModel {
interface FishingLogInfo {
fish_species_id?: number;
fish_name?: string;
catch_number?: number;
catch_unit?: string;
fish_size?: number;
fish_rarity?: number;
fish_condition?: string;
gear_usage?: string;
}
interface FishingLog {
fishing_log_id?: string;
trip_id: string;
start_at: Date;
end_at: Date;
start_lat: number;
start_lon: number;
haul_lat: number;
haul_lon: number;
status: number;
weather_description: string;
info?: FishingLogInfo[];
sync: boolean;
}
interface NewFishingLogRequest {
trip_id: string;
start_at: Date;
start_lat: number;
start_lon: number;
weather_description: string;
}
}

View File

@@ -0,0 +1,6 @@
declare namespace SgwModel {
interface FishingGear {
name: string;
number: string;
}
}

View File

@@ -0,0 +1,17 @@
declare namespace SgwModel {
// interface PhotoBasicInfo {
// type: 'ship' | 'people';
// id: string;
// }
interface PhotoGetParams {
type: 'ship' | 'people';
id: string;
}
interface PhotoUploadParams {
type: 'ship' | 'people';
id: string;
file: File;
}
}

View File

@@ -0,0 +1,24 @@
declare namespace SgwModel {
interface Port {
id: number;
name: string;
type: string;
classification: string;
position_point: string;
has_origin_confirm: boolean;
province_code: string;
updated_at: string;
is_deleted: boolean;
}
interface PortQueryParams extends MasterModel.SearchPaginationBody {
order?: string;
metadata?: {
province_code?: string;
};
}
interface PortQueryResponse extends MasterModel.PaginationReponse {
ports: Port[];
}
}

View File

@@ -0,0 +1,89 @@
declare namespace SgwModel {
interface ShipBaseInfo {
name?: string;
reg_number?: string;
ship_type?: number;
ship_length?: number;
ship_power?: number;
home_port?: number;
fishing_license_number?: string;
fishing_license_expiry_date?: string;
ship_group_id?: string | null; // Lưu ý: Update có thể cần null để gỡ nhóm
}
interface ShipType {
id?: number;
name?: string;
description?: string;
}
interface ShipMetadata {
crew_count?: number;
home_port?: string;
home_port_point?: string;
ship_type?: string;
trip_arrival_port?: string;
trip_arrival_port_point?: string;
trip_arrival_time?: Date;
trip_depart_port?: string;
trip_depart_port_point?: string;
trip_departure_time?: Date;
trip_id?: string;
trip_name?: string;
trip_state?: number;
}
interface ShipDetail extends ShipBaseInfo {
id?: string;
thing_id?: string;
owner_id?: string;
imo_number?: string;
mmsi_number?: string;
province_code?: string;
created_at?: Date | string;
updated_at?: Date | string;
metadata?: ShipMetadata;
}
interface ShipCreateParams extends ShipBaseInfo {
thing_id?: string;
}
interface ShipUpdateParams {
metadata?: Record<string, unknown>;
}
interface ShipQueryParams extends MasterModel.SearchPaginationBody {
order?: string;
registration_number?: string;
ship_type?: number;
ship_group_id?: string;
thing_id?: string;
}
interface ShipQueryResponse extends MasterModel.PaginationReponse {
ships: ShipDetail[];
}
// Ship Group
interface ShipGroupBaseInfo {
name: string;
owner_id?: string;
description?: string;
}
interface ShipGroup extends ShipGroupBaseInfo {
id: string;
created_at?: Date | string;
updated_at?: Date | string;
metadata?: Record<string, unknown>;
}
interface GroupShipResponse extends ShipGroupBaseInfo {
id?: string;
}
interface ShipGroupCreateParams extends ShipGroupBaseInfo {
metadata?: Record<string, unknown>;
}
interface ShipGroupUpdateParams extends ShipGroupBaseInfo {
metadata?: Record<string, unknown>;
}
}

111
src/services/slave/sgw/typings/trip.d.ts vendored Normal file
View File

@@ -0,0 +1,111 @@
declare namespace SgwModel {
interface TripBasicInfo {
name: string;
departure_time: string;
arrival_time: string;
trip_status: number;
ship_name: string;
departure_port_id: number;
arrival_port_id: number;
fishing_ground_codes: number[];
trip_cost: TripCost[];
notes: string | null;
fishing_gears?: FishingGear[];
}
interface Trip extends TripBasicInfo {
id: string;
ship_id: string;
ship_length: number;
vms_id: string;
crews?: TripCrews[];
total_catch_weight: number | null;
total_species_caught: number | null;
trip_status: number;
approved_by: string;
fishing_logs: FishingLog[] | null;
sync: boolean;
}
interface TripQueryParams extends MasterModel.SearchPaginationBody {
order?: string;
metadata?: {
from?: string;
to?: string;
ship_name?: string;
reg_number?: string;
province_code?: string;
owner_id?: string;
ship_id?: string;
status?: string;
};
}
interface TripQueryResponse extends MasterModel.PaginationReponse {
trips: Trip[];
}
interface TripUpdateParams extends Partial<TripBasicInfo> {
crews?: TripCrews[];
}
interface TripDeleteParams {
trip_ids: string[];
}
interface TripUpdateStateRequest {
status: number;
note?: string;
}
interface TripCreateParams extends Partial<TripBasicInfo> {
crews?: TripCrews[];
}
interface TripCost {
type: string;
unit: string;
amount: string;
total_cost: string;
cost_per_unit: string;
}
// Trip Crews
interface TripCrewPerson {
personal_id: string;
name: string;
phone: string;
email: string;
birth_date: Date;
note: string;
address: string;
created_at: Date;
updated_at: Date;
}
interface TripCrews {
role: string;
joined_at: Date;
left_at: Date | null;
note: string | null;
Person: TripCrewPerson;
}
interface TripCrewBasicInfo {
personal_id: string;
role: string;
note?: string;
}
interface TripCrewCreateParams extends Partial<TripCrewBasicInfo> {
trip_id: string;
}
interface TripCrewUpdateParams extends Partial<TripCrewBasicInfo> {
trip_id: string;
}
interface TripCrewQueryResponse {
trip_crews: TripCrews[];
total?: number;
}
}

View File

@@ -0,0 +1,8 @@
declare namespace WsTypes {
interface WsThingResponse {
thing_id?: string;
key?: string;
data?: string;
time?: number;
}
}

View File

@@ -0,0 +1,38 @@
declare namespace SgwModel {
// Banzone Management
interface ZoneBasicInfo {
name?: string;
province_code?: string;
type?: number;
conditions?: Condition[];
description?: string;
geometry?: string;
enabled?: boolean;
}
interface Banzone extends ZoneBasicInfo {
id?: string;
created_at?: Date;
updated_at?: Date;
}
interface Condition {
max?: number;
min?: number;
type?: 'length_limit' | 'month_range' | 'date_range';
to?: number;
from?: number;
}
interface Geom {
geom_type?: number;
geom_poly?: string;
geom_lines?: string;
geom_point?: string;
geom_radius?: number;
}
interface ZoneResponse extends Partial<MasterModel.PaginationResponse> {
banzones?: Banzone[];
}
}

View File

@@ -2,8 +2,11 @@
// 该文件由 OneAPI 自动生成,请勿手动修改! // 该文件由 OneAPI 自动生成,请勿手动修改!
declare namespace SpoleModel { declare namespace SpoleModel {
interface ThingMedata extends MasterModel.ThingMetadata {} interface ThingMetdadata extends MasterModel.ThingReponseMetadata {
uptime?: number;
}
type SpoleThingsResponse = MasterModel.ThingsResponse<SpoleModel.ThingMedata>; type SpoleThingsResponse =
type SpoleThing = MasterModel.Thing<SpoleModel.ThingMedata>; MasterModel.ThingsResponse<SpoleModel.ThingMetdadata>;
type SpoleThing = MasterModel.Thing<SpoleModel.ThingMetdadata>;
} }