feat: add camera configuration translations and enhance Camera components with internationalization support

This commit is contained in:
2026-02-09 21:55:56 +07:00
parent 674d53bcc5
commit 9d211ed43c
7 changed files with 489 additions and 80 deletions

View File

@@ -44,4 +44,58 @@ export default {
'master.devices.location.placeholder': 'Enter data',
'master.devices.location.update.success': 'Location updated successfully',
'master.devices.location.update.error': 'Location update failed',
// Camera translations
'master.camera.loading': 'Loading...',
'master.camera.config.success': 'Configuration sent successfully',
'master.camera.config.error.deviceOffline':
'Device is offline, cannot send configuration',
'master.camera.config.error.missingConfig':
'Missing device configuration information',
'master.camera.config.error.mqttNotConnected': 'MQTT not connected',
// Camera Form Modal
'master.camera.form.title.add': 'Add New Camera',
'master.camera.form.title.edit': 'Edit Camera',
'master.camera.form.name': 'Name',
'master.camera.form.name.placeholder': 'Enter name',
'master.camera.form.name.required': 'Please enter name',
'master.camera.form.type': 'Type',
'master.camera.form.type.required': 'Please select type',
'master.camera.form.username': 'Username',
'master.camera.form.username.placeholder': 'Enter username',
'master.camera.form.username.required': 'Please enter username',
'master.camera.form.password': 'Password',
'master.camera.form.password.placeholder': 'Enter password',
'master.camera.form.password.required': 'Please enter password',
'master.camera.form.ip': 'IP Address',
'master.camera.form.ip.placeholder': '192.168.1.10',
'master.camera.form.ip.required': 'Please enter IP address',
'master.camera.form.rtspPort': 'RTSP Port',
'master.camera.form.rtspPort.required': 'Please enter RTSP port',
'master.camera.form.httpPort': 'HTTP Port',
'master.camera.form.httpPort.required': 'Please enter HTTP port',
'master.camera.form.stream': 'Stream',
'master.camera.form.stream.required': 'Please enter stream',
'master.camera.form.channel': 'Channel',
'master.camera.form.channel.required': 'Please enter channel',
'master.camera.form.cancel': 'Cancel',
'master.camera.form.submit': 'OK',
'master.camera.form.update': 'Update',
// Camera Table
'master.camera.table.add': 'Add New Camera',
'master.camera.table.column.name': 'Name',
'master.camera.table.column.type': 'Type',
'master.camera.table.column.ip': 'IP Address',
'master.camera.table.column.action': 'Actions',
'master.camera.table.offline.tooltip': 'Device is offline',
'master.camera.table.pagination': 'Showing {0}-{1} of {2} cameras',
// Camera Config V6
'master.camera.config.recording': 'Camera Recording',
'master.camera.config.send': 'Send',
'master.camera.config.alarmList': 'Alarm List',
'master.camera.config.selected': '{0} items selected',
'master.camera.config.clear': 'Clear',
'master.camera.config.recordingMode.none': 'No Recording',
'master.camera.config.recordingMode.alarm': 'On Alarm',
'master.camera.config.recordingMode.all': '24/7',
};

View File

@@ -44,4 +44,58 @@ export default {
'master.devices.location.placeholder': 'Nhập dữ liệu',
'master.devices.location.update.success': 'Cập nhật vị trí thành công',
'master.devices.location.update.error': 'Cập nhật vị trí thất bại',
// Camera translations
'master.camera.loading': 'Đang tải...',
'master.camera.config.success': 'Đã gửi cấu hình thành công',
'master.camera.config.error.deviceOffline':
'Thiết bị đang ngoại tuyến, không thể gửi cấu hình',
'master.camera.config.error.missingConfig':
'Thiếu thông tin cấu hình thiết bị',
'master.camera.config.error.mqttNotConnected': 'MQTT chưa kết nối',
// Camera Form Modal
'master.camera.form.title.add': 'Tạo mới camera',
'master.camera.form.title.edit': 'Chỉnh sửa camera',
'master.camera.form.name': 'Tên',
'master.camera.form.name.placeholder': 'Nhập tên',
'master.camera.form.name.required': 'Vui lòng nhập tên',
'master.camera.form.type': 'Loại',
'master.camera.form.type.required': 'Vui lòng chọn loại',
'master.camera.form.username': 'Tài khoản',
'master.camera.form.username.placeholder': 'Nhập tài khoản',
'master.camera.form.username.required': 'Vui lòng nhập tài khoản',
'master.camera.form.password': 'Mật khẩu',
'master.camera.form.password.placeholder': 'Nhập mật khẩu',
'master.camera.form.password.required': 'Vui lòng nhập mật khẩu',
'master.camera.form.ip': 'Địa chỉ IP',
'master.camera.form.ip.placeholder': '192.168.1.10',
'master.camera.form.ip.required': 'Vui lòng nhập địa chỉ IP',
'master.camera.form.rtspPort': 'Cổng RTSP',
'master.camera.form.rtspPort.required': 'Vui lòng nhập cổng RTSP',
'master.camera.form.httpPort': 'Cổng HTTP',
'master.camera.form.httpPort.required': 'Vui lòng nhập cổng HTTP',
'master.camera.form.stream': 'Luồng',
'master.camera.form.stream.required': 'Vui lòng nhập luồng',
'master.camera.form.channel': 'Kênh',
'master.camera.form.channel.required': 'Vui lòng nhập kênh',
'master.camera.form.cancel': 'Hủy',
'master.camera.form.submit': 'Đồng ý',
'master.camera.form.update': 'Cập nhật',
// Camera Table
'master.camera.table.add': 'Tạo mới camera',
'master.camera.table.column.name': 'Tên',
'master.camera.table.column.type': 'Loại',
'master.camera.table.column.ip': 'Địa chỉ IP',
'master.camera.table.column.action': 'Thao tác',
'master.camera.table.offline.tooltip': 'Thiết bị đang ngoại tuyến',
'master.camera.table.pagination': 'Hiển thị {0}-{1} của {2} camera',
// Camera Config V6
'master.camera.config.recording': 'Ghi dữ liệu camera',
'master.camera.config.send': 'Gửi đi',
'master.camera.config.alarmList': 'Danh sách cảnh báo',
'master.camera.config.selected': 'đã chọn {0} mục',
'master.camera.config.clear': 'Xóa',
'master.camera.config.recordingMode.none': 'Không ghi',
'master.camera.config.recordingMode.alarm': 'Theo cảnh báo',
'master.camera.config.recordingMode.all': '24/24',
};

View File

@@ -1,3 +1,4 @@
import { useIntl } from '@umijs/max';
import {
Button,
Col,
@@ -33,6 +34,7 @@ const CameraFormModal: React.FC<CameraFormModalProps> = ({
editingCamera,
}) => {
const [form] = Form.useForm<MasterModel.Camera>();
const intl = useIntl();
const isEditMode = !!editingCamera;
// Populate form when editing
@@ -73,15 +75,28 @@ const CameraFormModal: React.FC<CameraFormModalProps> = ({
return (
<Modal
title={isEditMode ? 'Chỉnh sửa camera' : 'Tạo mới camera'}
title={intl.formatMessage({
id: isEditMode
? 'master.camera.form.title.edit'
: 'master.camera.form.title.add',
defaultMessage: isEditMode ? 'Chỉnh sửa camera' : 'Tạo mới camera',
})}
open={open}
onCancel={handleCancel}
footer={[
<Button key="cancel" onClick={handleCancel}>
Hủy
{intl.formatMessage({
id: 'master.camera.form.cancel',
defaultMessage: 'Hủy',
})}
</Button>,
<Button key="submit" type="primary" onClick={handleSubmit}>
{isEditMode ? 'Cập nhật' : 'Đồng ý'}
{intl.formatMessage({
id: isEditMode
? 'master.camera.form.update'
: 'master.camera.form.submit',
defaultMessage: isEditMode ? 'Cập nhật' : 'Đồng ý',
})}
</Button>,
]}
width={500}
@@ -99,63 +114,159 @@ const CameraFormModal: React.FC<CameraFormModalProps> = ({
}}
>
<Form.Item
label="Tên"
label={intl.formatMessage({
id: 'master.camera.form.name',
defaultMessage: 'Tên',
})}
name="name"
rules={[{ required: true, message: 'Vui lòng nhập tên' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.name.required',
defaultMessage: 'Vui lòng nhập tên',
}),
},
]}
>
<Input placeholder="nhập dữ liệu" />
<Input
placeholder={intl.formatMessage({
id: 'master.camera.form.name.placeholder',
defaultMessage: 'nhập dữ liệu',
})}
/>
</Form.Item>
<Form.Item
label="Loại"
label={intl.formatMessage({
id: 'master.camera.form.type',
defaultMessage: 'Loại',
})}
name="cate_id"
rules={[{ required: true, message: 'Vui lòng chọn loại' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.type.required',
defaultMessage: 'Vui lòng chọn loại',
}),
},
]}
>
<Select options={CAMERA_TYPES} />
</Form.Item>
<Form.Item
label="Tài khoản"
label={intl.formatMessage({
id: 'master.camera.form.username',
defaultMessage: 'Tài khoản',
})}
name="username"
rules={[{ required: true, message: 'Vui lòng nhập tài khoản' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.username.required',
defaultMessage: 'Vui lòng nhập tài khoản',
}),
},
]}
>
<Input placeholder="nhập tài khoản" autoComplete="off" />
<Input
placeholder={intl.formatMessage({
id: 'master.camera.form.username.placeholder',
defaultMessage: 'nhập tài khoản',
})}
autoComplete="off"
/>
</Form.Item>
<Form.Item
label="Mật khẩu"
label={intl.formatMessage({
id: 'master.camera.form.password',
defaultMessage: 'Mật khẩu',
})}
name="password"
rules={[{ required: true, message: 'Vui lòng nhập mật khẩu' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.password.required',
defaultMessage: 'Vui lòng nhập mật khẩu',
}),
},
]}
>
<Input.Password
placeholder="nhập mật khẩu"
placeholder={intl.formatMessage({
id: 'master.camera.form.password.placeholder',
defaultMessage: 'nhập mật khẩu',
})}
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label="Địa chỉ IP"
label={intl.formatMessage({
id: 'master.camera.form.ip',
defaultMessage: 'Địa chỉ IP',
})}
name="ip"
rules={[{ required: true, message: 'Vui lòng nhập địa chỉ IP' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.ip.required',
defaultMessage: 'Vui lòng nhập địa chỉ IP',
}),
},
]}
>
<Input placeholder="192.168.1.10" />
<Input
placeholder={intl.formatMessage({
id: 'master.camera.form.ip.placeholder',
defaultMessage: '192.168.1.10',
})}
/>
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="Cổng RTSP"
label={intl.formatMessage({
id: 'master.camera.form.rtspPort',
defaultMessage: 'Cổng RTSP',
})}
name="rtsp_port"
rules={[{ required: true, message: 'Vui lòng nhập cổng RTSP' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.rtspPort.required',
defaultMessage: 'Vui lòng nhập cổng RTSP',
}),
},
]}
>
<InputNumber style={{ width: '100%' }} min={0} max={65535} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="Cổng HTTP"
label={intl.formatMessage({
id: 'master.camera.form.httpPort',
defaultMessage: 'Cổng HTTP',
})}
name="http_port"
rules={[{ required: true, message: 'Vui lòng nhập cổng HTTP' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.httpPort.required',
defaultMessage: 'Vui lòng nhập cổng HTTP',
}),
},
]}
>
<InputNumber style={{ width: '100%' }} min={0} max={65535} />
</Form.Item>
@@ -165,18 +276,40 @@ const CameraFormModal: React.FC<CameraFormModalProps> = ({
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="Luồng"
label={intl.formatMessage({
id: 'master.camera.form.stream',
defaultMessage: 'Luồng',
})}
name="stream"
rules={[{ required: true, message: 'Vui lòng nhập luồng' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.stream.required',
defaultMessage: 'Vui lòng nhập luồng',
}),
},
]}
>
<InputNumber style={{ width: '100%' }} min={0} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="Kênh"
label={intl.formatMessage({
id: 'master.camera.form.channel',
defaultMessage: 'Kênh',
})}
name="channel"
rules={[{ required: true, message: 'Vui lòng nhập kênh' }]}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'master.camera.form.channel.required',
defaultMessage: 'Vui lòng nhập kênh',
}),
},
]}
>
<InputNumber style={{ width: '100%' }} min={0} />
</Form.Item>

View File

@@ -4,6 +4,7 @@ import {
PlusOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Card, Space, Table, theme, Tooltip } from 'antd';
import { useState } from 'react';
@@ -27,6 +28,7 @@ const CameraTable: React.FC<CameraTableProps> = ({
isOnline = false,
}) => {
const { token } = theme.useToken();
const intl = useIntl();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const handleReload = () => {
@@ -48,7 +50,10 @@ const CameraTable: React.FC<CameraTableProps> = ({
const columns = [
{
title: 'Tên',
title: intl.formatMessage({
id: 'master.camera.table.column.name',
defaultMessage: 'Tên',
}),
dataIndex: 'name',
key: 'name',
render: (text: string) => (
@@ -56,22 +61,40 @@ const CameraTable: React.FC<CameraTableProps> = ({
),
},
{
title: 'Loại',
title: intl.formatMessage({
id: 'master.camera.table.column.type',
defaultMessage: 'Loại',
}),
dataIndex: 'cate_id',
key: 'cate_id',
render: (text: string) => text || '-',
},
{
title: 'Địa chỉ IP',
title: intl.formatMessage({
id: 'master.camera.table.column.ip',
defaultMessage: 'Địa chỉ IP',
}),
dataIndex: 'ip',
key: 'ip',
render: (text: string) => text || '-',
},
{
title: 'Thao tác',
title: intl.formatMessage({
id: 'master.camera.table.column.action',
defaultMessage: 'Thao tác',
}),
key: 'action',
render: (_: any, record: MasterModel.Camera) => (
<Tooltip title={!isOnline ? 'Thiết bị đang ngoại tuyến' : ''}>
<Tooltip
title={
!isOnline
? intl.formatMessage({
id: 'master.camera.table.offline.tooltip',
defaultMessage: 'Thiết bị đang ngoại tuyến',
})
: ''
}
>
<Button
size="small"
icon={<EditOutlined />}
@@ -86,14 +109,26 @@ const CameraTable: React.FC<CameraTableProps> = ({
return (
<Card bodyStyle={{ padding: 16 }}>
<Space style={{ marginBottom: 16 }}>
<Tooltip title={!isOnline ? 'Thiết bị đang ngoại tuyến' : ''}>
<Tooltip
title={
!isOnline
? intl.formatMessage({
id: 'master.camera.table.offline.tooltip',
defaultMessage: 'Thiết bị đang ngoại tuyến',
})
: ''
}
>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={onCreateCamera}
disabled={!isOnline}
>
Tạo mới camera
{intl.formatMessage({
id: 'master.camera.table.add',
defaultMessage: 'Tạo mới camera',
})}
</Button>
</Tooltip>
<Button
@@ -101,7 +136,16 @@ const CameraTable: React.FC<CameraTableProps> = ({
onClick={handleReload}
loading={loading}
/>
<Tooltip title={!isOnline ? 'Thiết bị đang ngoại tuyến' : ''}>
<Tooltip
title={
!isOnline
? intl.formatMessage({
id: 'master.camera.table.offline.tooltip',
defaultMessage: 'Thiết bị đang ngoại tuyến',
})
: ''
}
>
<Button
icon={<DeleteOutlined />}
onClick={handleDelete}
@@ -126,7 +170,17 @@ const CameraTable: React.FC<CameraTableProps> = ({
pagination={{
size: 'small',
showTotal: (total: number, range: [number, number]) =>
`Hiển thị ${range[0]}-${range[1]} của ${total} camera`,
intl.formatMessage(
{
id: 'master.camera.table.pagination',
defaultMessage: 'Hiển thị {0}-{1} của {2} camera',
},
{
0: range[0],
1: range[1],
2: total,
},
),
pageSize: 10,
}}
/>

View File

@@ -1,13 +1,10 @@
import { useIntl } from '@umijs/max';
import { Button, Card, Select, Typography } from 'antd';
import { useState } from 'react';
import { useMemo, useState } from 'react';
const { Text } = Typography;
// Recording modes for V5 - chỉ có không ghi và ghi 24/24
const RECORDING_MODES = [
{ label: 'Không ghi', value: 'none' },
{ label: 'Ghi 24/24', value: '24/7' },
];
interface CameraV5Props {
thing: MasterModel.Thing | null;
@@ -18,9 +15,29 @@ const CameraV5: React.FC<CameraV5Props> = ({
thing,
initialRecordingMode = 'none',
}) => {
const [recordingMode, setRecordingMode] = useState(initialRecordingMode);
const intl = useIntl();
console.log('ConfigCameraV5 - thing:', thing);
const RECORDING_MODES = useMemo(
() => [
{
label: intl.formatMessage({
id: 'master.camera.config.recordingMode.none',
defaultMessage: 'Không ghi',
}),
value: 'none',
},
{
label: intl.formatMessage({
id: 'master.camera.config.recordingMode.all',
defaultMessage: '24/24',
}),
value: 'all',
},
],
[intl],
);
const [recordingMode, setRecordingMode] = useState(initialRecordingMode);
const handleSubmit = () => {
console.log('Submit recording mode:', recordingMode);
@@ -30,23 +47,30 @@ const CameraV5: React.FC<CameraV5Props> = ({
return (
<Card bodyStyle={{ padding: 16 }}>
{/* Recording Mode */}
<div style={{ marginBottom: 24 }}>
<Text strong style={{ display: 'block', marginBottom: 8 }}>
Ghi dữ liệu camera
</Text>
<Select
value={recordingMode}
onChange={setRecordingMode}
options={RECORDING_MODES}
style={{ width: 200 }}
/>
</div>
{/* Submit Button */}
<div style={{ textAlign: 'center' }}>
<Button type="primary" onClick={handleSubmit}>
Gửi đi
</Button>
<div className="mb-6">
<div className="flex flex-col sm:flex-row gap-2 sm:gap-4 items-start sm:items-end">
<div className="w-full sm:w-1/3 lg:w-1/4">
<Text strong className="block mb-2">
{intl.formatMessage({
id: 'master.camera.config.recording',
defaultMessage: 'Ghi dữ liệu camera',
})}
</Text>
<Select
value={recordingMode}
onChange={setRecordingMode}
options={RECORDING_MODES}
className="w-full"
popupMatchSelectWidth={false}
/>
</div>
<Button type="primary" onClick={handleSubmit}>
{intl.formatMessage({
id: 'master.camera.config.send',
defaultMessage: 'Gửi đi',
})}
</Button>
</div>
</div>
</Card>
);

View File

@@ -1,5 +1,5 @@
import { apiQueryConfigAlarm } from '@/services/master/MessageController';
import { useModel } from '@umijs/max';
import { useIntl, useModel } from '@umijs/max';
import {
Button,
Card,
@@ -10,17 +10,10 @@ import {
Tooltip,
Typography,
} from 'antd';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
const { Text } = Typography;
// Recording modes for V6
const RECORDING_MODES = [
{ label: 'Không ghi', value: 'none' },
{ label: 'Theo cảnh báo', value: 'alarm' },
{ label: '24/24', value: 'all' },
];
interface CameraV6Props {
thing: MasterModel.Thing | null;
cameraConfig?: MasterModel.CameraV6 | null;
@@ -35,6 +28,7 @@ const CameraV6: React.FC<CameraV6Props> = ({
isOnline = false,
}) => {
const { token } = theme.useToken();
const intl = useIntl();
const { initialState } = useModel('@@initialState');
const [selectedAlerts, setSelectedAlerts] = useState<string[]>([]);
const [recordingMode, setRecordingMode] =
@@ -43,6 +37,34 @@ const CameraV6: React.FC<CameraV6Props> = ({
null,
);
// Recording modes for V6 - using useMemo for i18n
const RECORDING_MODES = useMemo(
() => [
{
label: intl.formatMessage({
id: 'master.camera.config.recordingMode.none',
defaultMessage: 'Không ghi',
}),
value: 'none',
},
{
label: intl.formatMessage({
id: 'master.camera.config.recordingMode.alarm',
defaultMessage: 'Theo cảnh báo',
}),
value: 'alarm',
},
{
label: intl.formatMessage({
id: 'master.camera.config.recordingMode.all',
defaultMessage: '24/24',
}),
value: 'all',
},
],
[intl],
);
// Initialize states from cameraConfig when it's available
useEffect(() => {
if (cameraConfig) {
@@ -125,7 +147,10 @@ const CameraV6: React.FC<CameraV6Props> = ({
<div className="flex flex-col sm:flex-row gap-2 sm:gap-4 items-start sm:items-end">
<div className="w-full sm:w-1/3 lg:w-1/4">
<Text strong className="block mb-2">
Ghi dữ liệu camera
{intl.formatMessage({
id: 'master.camera.config.recording',
defaultMessage: 'Ghi dữ liệu camera',
})}
</Text>
<Select
value={recordingMode}
@@ -135,13 +160,25 @@ const CameraV6: React.FC<CameraV6Props> = ({
popupMatchSelectWidth={false}
/>
</div>
<Tooltip title={!isOnline ? 'Thiết bị đang ngoại tuyến' : ''}>
<Tooltip
title={
!isOnline
? intl.formatMessage({
id: 'master.camera.table.offline.tooltip',
defaultMessage: 'Thiết bị đang ngoại tuyến',
})
: ''
}
>
<Button
type="primary"
onClick={handleSubmitConfig}
disabled={!isOnline}
>
Gửi đi
{intl.formatMessage({
id: 'master.camera.config.send',
defaultMessage: 'Gửi đi',
})}
</Button>
</Tooltip>
</div>
@@ -151,7 +188,10 @@ const CameraV6: React.FC<CameraV6Props> = ({
{recordingMode === 'alarm' && (
<div>
<Text strong className="block mb-2">
Danh sách cảnh báo
{intl.formatMessage({
id: 'master.camera.config.alarmList',
defaultMessage: 'Danh sách cảnh báo',
})}
</Text>
<div
@@ -161,9 +201,22 @@ const CameraV6: React.FC<CameraV6Props> = ({
borderColor: token.colorBorder,
}}
>
<Text type="secondary">đã chọn {selectedAlerts.length} mục</Text>
<Text type="secondary">
{intl.formatMessage(
{
id: 'master.camera.config.selected',
defaultMessage: 'đã chọn {0} mục',
},
{
0: selectedAlerts.length,
},
)}
</Text>
<Button type="link" onClick={handleClearAlerts}>
Xóa
{intl.formatMessage({
id: 'master.camera.config.clear',
defaultMessage: 'Xóa',
})}
</Button>
</div>

View File

@@ -3,7 +3,7 @@ import { apiGetThingDetail } from '@/services/master/ThingController';
import { mqttClient } from '@/utils/mqttClient';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { history, useModel, useParams } from '@umijs/max';
import { history, useIntl, useModel, useParams } from '@umijs/max';
import { Badge, Button, Col, message, Row, Space, Spin } from 'antd';
import { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
@@ -14,6 +14,7 @@ import ConfigCameraV6 from './components/ConfigCameraV6';
const CameraConfigPage = () => {
const { thingId } = useParams<{ thingId: string }>();
const intl = useIntl();
const [isModalVisible, setIsModalVisible] = useState(false);
const [loading, setLoading] = useState(true);
const [cameraLoading, setCameraLoading] = useState(false);
@@ -143,12 +144,22 @@ const CameraConfigPage = () => {
// Check if device is online
if (!isOnline) {
message.error('Thiết bị đang ngoại tuyến, không thể gửi cấu hình');
message.error(
intl.formatMessage({
id: 'master.camera.config.error.deviceOffline',
defaultMessage: 'Thiết bị đang ngoại tuyến, không thể gửi cấu hình',
}),
);
return false;
}
if (!thing?.metadata?.cfg_channel_id || !thing?.metadata?.external_id) {
message.error('Thiếu thông tin cấu hình thiết bị');
message.error(
intl.formatMessage({
id: 'master.camera.config.error.missingConfig',
defaultMessage: 'Thiếu thông tin cấu hình thiết bị',
}),
);
return false;
}
@@ -175,10 +186,20 @@ const CameraConfigPage = () => {
if (mqttClient.isConnected()) {
mqttClient.publish(pubTopic, payload);
message.success('Đã gửi cấu hình thành công');
message.success(
intl.formatMessage({
id: 'master.camera.config.success',
defaultMessage: 'Đã gửi cấu hình thành công',
}),
);
return true;
} else {
message.error('MQTT chưa kết nối');
message.error(
intl.formatMessage({
id: 'master.camera.config.error.mqttNotConnected',
defaultMessage: 'MQTT chưa kết nối',
}),
);
return false;
}
};
@@ -266,10 +287,26 @@ const CameraConfigPage = () => {
icon={<ArrowLeftOutlined />}
onClick={() => history.push('/manager/devices')}
/>
<span>{thing?.name || 'Loading...'}</span>
<span>
{thing?.name ||
intl.formatMessage({
id: 'master.camera.loading',
defaultMessage: 'Loading...',
})}
</span>
<Badge
status={isOnline ? 'success' : 'default'}
text={isOnline ? 'Trực tuyến' : 'Ngoại tuyến'}
text={
isOnline
? intl.formatMessage({
id: 'master.devices.online',
defaultMessage: 'Trực tuyến',
})
: intl.formatMessage({
id: 'master.devices.offline',
defaultMessage: 'Ngoại tuyến',
})
}
/>
</Space>
),