feat: add camera configuration translations and enhance Camera components with internationalization support
This commit is contained in:
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user