From ea5fc0a617be321db7ecb1be6b8d75385fc9f28f Mon Sep 17 00:00:00 2001 From: MinhNN Date: Tue, 10 Feb 2026 15:07:46 +0700 Subject: [PATCH] feat: Refactor theme management and localization for camera and terminal components --- src/app.tsx | 8 +- src/components/Theme/ThemeSwitcher.tsx | 11 +- src/components/Theme/ThemeSwitcherAuth.tsx | 5 +- src/constants/index.ts | 2 + src/locales/en-US/master/master-thing-en.ts | 117 +++++++++++------- src/locales/vi-VN/master/master-thing-vi.ts | 116 ++++++++++------- src/pages/Auth/ForgotPassword/index.tsx | 6 +- src/pages/Auth/index.tsx | 17 +-- .../Camera/components/CameraFormModal.tsx | 54 ++++---- .../Device/Camera/components/CameraTable.tsx | 18 +-- .../Camera/components/ConfigCameraV5.tsx | 8 +- .../Camera/components/ConfigCameraV6.tsx | 18 +-- src/pages/Manager/Device/Camera/index.tsx | 10 +- src/pages/Manager/Device/Terminal/index.tsx | 38 +++--- src/utils/storage.ts | 37 +++++- 15 files changed, 265 insertions(+), 200 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 194c618..7a9a4ca 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,5 +1,6 @@ // 运行时配置 +import { getTheme } from '@/utils/storage'; import { getLocale, history, Link, RunTimeLayoutConfig } from '@umijs/max'; import { ConfigProvider } from 'antd'; import dayjs from 'dayjs'; @@ -10,7 +11,6 @@ import IconFont from './components/IconFont'; import LanguageSwitcher from './components/Lang/LanguageSwitcher'; import ThemeProvider from './components/Theme/ThemeProvider'; import ThemeSwitcher from './components/Theme/ThemeSwitcher'; -import { THEME_KEY } from './constants'; import { ROUTE_FORGOT_PASSWORD, ROUTE_LOGIN } from './constants/routes'; import NotFoundPage from './pages/Exception/NotFound'; import UnAccessPage from './pages/Exception/UnAccess'; @@ -52,8 +52,7 @@ export async function getInitialState(): Promise { // Public routes that don't require authentication if (publicRoutes.includes(pathname)) { - const currentTheme = - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') || 'light'; + const currentTheme = (getTheme() as 'light' | 'dark') || 'light'; return { theme: currentTheme, }; @@ -101,8 +100,7 @@ export async function getInitialState(): Promise { } }; const resp = await getUserProfile(); - const currentTheme = - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') || 'light'; + const currentTheme = (getTheme() as 'light' | 'dark') || 'light'; return { getUserProfile: getUserProfile!, currentUserProfile: resp, diff --git a/src/components/Theme/ThemeSwitcher.tsx b/src/components/Theme/ThemeSwitcher.tsx index 439facb..951bd41 100644 --- a/src/components/Theme/ThemeSwitcher.tsx +++ b/src/components/Theme/ThemeSwitcher.tsx @@ -1,4 +1,4 @@ -import { THEME_KEY } from '@/constants'; +import { setTheme } from '@/utils/storage'; import { MoonOutlined, SunOutlined } from '@ant-design/icons'; import { useModel } from '@umijs/max'; import { Segmented } from 'antd'; @@ -34,7 +34,7 @@ const ThemeSwitcher: React.FC = ({ className }) => { if (!supportsViewTransition) { // Fallback: just change theme without animation - localStorage.setItem(THEME_KEY, newTheme); + setTheme(newTheme); setIsDark(newTheme === 'dark'); window.dispatchEvent( new CustomEvent('theme-change', { detail: { theme: newTheme } }), @@ -58,7 +58,7 @@ const ThemeSwitcher: React.FC = ({ className }) => { // Start the view transition const transition = (document as any).startViewTransition(() => { - localStorage.setItem(THEME_KEY, newTheme); + setTheme(newTheme); setIsDark(newTheme === 'dark'); window.dispatchEvent( new CustomEvent('theme-change', { detail: { theme: newTheme } }), @@ -107,8 +107,3 @@ const ThemeSwitcher: React.FC = ({ className }) => { }; export default ThemeSwitcher; - -// Helper function để get theme từ localStorage -export const getTheme = (): 'light' | 'dark' => { - return (localStorage.getItem(THEME_KEY) as 'light' | 'dark') || 'light'; -}; diff --git a/src/components/Theme/ThemeSwitcherAuth.tsx b/src/components/Theme/ThemeSwitcherAuth.tsx index 4927e56..7c202d7 100644 --- a/src/components/Theme/ThemeSwitcherAuth.tsx +++ b/src/components/Theme/ThemeSwitcherAuth.tsx @@ -1,6 +1,5 @@ -import { THEME_KEY } from '@/constants'; +import { getTheme, setTheme } from '@/utils/storage'; import React, { useEffect, useState } from 'react'; -import { getTheme } from './ThemeSwitcher'; import './style.less'; const ThemeSwitcherAuth = () => { @@ -21,7 +20,7 @@ const ThemeSwitcherAuth = () => { const handleSwitch = (e: React.ChangeEvent) => { const newTheme = e.target.checked ? 'dark' : 'light'; - localStorage.setItem(THEME_KEY, newTheme); + setTheme(newTheme); setIsDark(newTheme === 'dark'); window.dispatchEvent( new CustomEvent('theme-change', { detail: { theme: newTheme } }), diff --git a/src/constants/index.ts b/src/constants/index.ts index 43d0f0d..09e0792 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -11,6 +11,7 @@ export const DURATION_POLLING_PRESENTATIONS = 120000; //milliseconds export const STATUS_NORMAL = 0; export const STATUS_WARNING = 1; export const STATUS_DANGEROUS = 2; + export const STATUS_SOS = 3; export const COLOR_DISCONNECT = '#d9d9d9'; @@ -22,6 +23,7 @@ export const COLOR_SOS = '#ff0000'; export const ACCESS_TOKEN = 'access_token'; export const REFRESH_TOKEN = 'refresh_token'; export const THEME_KEY = 'theme'; +export const TERMINAL_THEME_KEY = 'terminal_theme_key'; // Global Constants export const LIMIT_TREE_LEVEL = 5; export const DEFAULT_PAGE_SIZE = 5; diff --git a/src/locales/en-US/master/master-thing-en.ts b/src/locales/en-US/master/master-thing-en.ts index 3e1b13d..710f39e 100644 --- a/src/locales/en-US/master/master-thing-en.ts +++ b/src/locales/en-US/master/master-thing-en.ts @@ -46,56 +46,79 @@ export default { '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': + 'master.devices.camera.loading': 'Loading...', + 'master.devices.camera.config.success': 'Configuration sent successfully', + 'master.devices.camera.config.error.deviceOffline': 'Device is offline, cannot send configuration', - 'master.camera.config.error.missingConfig': + 'master.devices.camera.config.error.missingConfig': 'Missing device configuration information', - 'master.camera.config.error.mqttNotConnected': 'MQTT not connected', + 'master.devices.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', + 'master.devices.camera.form.title.add': 'Add New Camera', + 'master.devices.camera.form.title.edit': 'Edit Camera', + 'master.devices.camera.form.name': 'Name', + 'master.devices.camera.form.name.placeholder': 'Enter name', + 'master.devices.camera.form.name.required': 'Please enter name', + 'master.devices.camera.form.type': 'Type', + 'master.devices.camera.form.type.required': 'Please select type', + 'master.devices.camera.form.username': 'Username', + 'master.devices.camera.form.username.placeholder': 'Enter username', + 'master.devices.camera.form.username.required': 'Please enter username', + 'master.devices.camera.form.password': 'Password', + 'master.devices.camera.form.password.placeholder': 'Enter password', + 'master.devices.camera.form.password.required': 'Please enter password', + 'master.devices.camera.form.ip': 'IP Address', + 'master.devices.camera.form.ip.placeholder': '192.168.1.10', + 'master.devices.camera.form.ip.required': 'Please enter IP address', + 'master.devices.camera.form.rtspPort': 'RTSP Port', + 'master.devices.camera.form.rtspPort.required': 'Please enter RTSP port', + 'master.devices.camera.form.httpPort': 'HTTP Port', + 'master.devices.camera.form.httpPort.required': 'Please enter HTTP port', + 'master.devices.camera.form.stream': 'Stream', + 'master.devices.camera.form.stream.required': 'Please enter stream', + 'master.devices.camera.form.channel': 'Channel', + 'master.devices.camera.form.channel.required': 'Please enter channel', + 'master.devices.camera.form.cancel': 'Cancel', + 'master.devices.camera.form.submit': 'OK', + 'master.devices.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', + 'master.devices.camera.table.add': 'Add New Camera', + 'master.devices.camera.table.column.name': 'Name', + 'master.devices.camera.table.column.type': 'Type', + 'master.devices.camera.table.column.ip': 'IP Address', + 'master.devices.camera.table.column.action': 'Actions', + 'master.devices.camera.table.offline.tooltip': 'Device is offline', + 'master.devices.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', + 'master.devices.camera.config.recording': 'Camera Recording', + 'master.devices.camera.config.send': 'Send', + 'master.devices.camera.config.alarmList': 'Alarm List', + 'master.devices.camera.config.selected': '{0} items selected', + 'master.devices.camera.config.clear': 'Clear', + 'master.devices.camera.config.recordingMode.none': 'No Recording', + 'master.devices.camera.config.recordingMode.alarm': 'On Alarm', + 'master.devices.camera.config.recordingMode.all': '24/7', + + // Terminal translations + 'master.devices.terminal.pageTitle': 'Terminal', + 'master.devices.terminal.loadDeviceError': 'Cannot load device information.', + 'master.devices.terminal.mqttError': 'Cannot connect to MQTT.', + 'master.devices.terminal.genericError': 'An error occurred', + 'master.devices.terminal.unsupported.title': + 'Device does not support terminal', + 'master.devices.terminal.unsupported.desc': + 'GMSv5 devices are not supported. Please use a different device.', + 'master.devices.terminal.missingChannel.title': + 'Missing control channel information', + 'master.devices.terminal.missingChannel.desc': + 'Device has not been configured with ctrl_channel_id, cannot open terminal.', + 'master.devices.terminal.missingCredential.title': + 'Missing authentication information', + 'master.devices.terminal.missingCredential.desc': + 'Current account has not been granted frontend_thing_id/frontend_thing_key.', + 'master.devices.terminal.offline': + 'Device is offline. Terminal is in view-only mode.', + 'master.devices.terminal.connecting': 'Preparing terminal session...', + 'master.devices.terminal.action.clear': 'Clear screen', + 'master.devices.terminal.action.theme': 'Theme', }; diff --git a/src/locales/vi-VN/master/master-thing-vi.ts b/src/locales/vi-VN/master/master-thing-vi.ts index 5702103..39179f6 100644 --- a/src/locales/vi-VN/master/master-thing-vi.ts +++ b/src/locales/vi-VN/master/master-thing-vi.ts @@ -46,56 +46,78 @@ export default { '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': + 'master.devices.camera.loading': 'Đang tải...', + 'master.devices.camera.config.success': 'Đã gửi cấu hình thành công', + 'master.devices.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': + 'master.devices.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', + 'master.devices.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', + 'master.devices.camera.form.title.add': 'Tạo mới camera', + 'master.devices.camera.form.title.edit': 'Chỉnh sửa camera', + 'master.devices.camera.form.name': 'Tên', + 'master.devices.camera.form.name.placeholder': 'Nhập tên', + 'master.devices.camera.form.name.required': 'Vui lòng nhập tên', + 'master.devices.camera.form.type': 'Loại', + 'master.devices.camera.form.type.required': 'Vui lòng chọn loại', + 'master.devices.camera.form.username': 'Tài khoản', + 'master.devices.camera.form.username.placeholder': 'Nhập tài khoản', + 'master.devices.camera.form.username.required': 'Vui lòng nhập tài khoản', + 'master.devices.camera.form.password': 'Mật khẩu', + 'master.devices.camera.form.password.placeholder': 'Nhập mật khẩu', + 'master.devices.camera.form.password.required': 'Vui lòng nhập mật khẩu', + 'master.devices.camera.form.ip': 'Địa chỉ IP', + 'master.devices.camera.form.ip.placeholder': '192.168.1.10', + 'master.devices.camera.form.ip.required': 'Vui lòng nhập địa chỉ IP', + 'master.devices.camera.form.rtspPort': 'Cổng RTSP', + 'master.devices.camera.form.rtspPort.required': 'Vui lòng nhập cổng RTSP', + 'master.devices.camera.form.httpPort': 'Cổng HTTP', + 'master.devices.camera.form.httpPort.required': 'Vui lòng nhập cổng HTTP', + 'master.devices.camera.form.stream': 'Luồng', + 'master.devices.camera.form.stream.required': 'Vui lòng nhập luồng', + 'master.devices.camera.form.channel': 'Kênh', + 'master.devices.camera.form.channel.required': 'Vui lòng nhập kênh', + 'master.devices.camera.form.cancel': 'Hủy', + 'master.devices.camera.form.submit': 'Đồng ý', + 'master.devices.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', + 'master.devices.camera.table.add': 'Tạo mới camera', + 'master.devices.camera.table.column.name': 'Tên', + 'master.devices.camera.table.column.type': 'Loại', + 'master.devices.camera.table.column.ip': 'Địa chỉ IP', + 'master.devices.camera.table.column.action': 'Thao tác', + 'master.devices.camera.table.offline.tooltip': 'Thiết bị đang ngoại tuyến', + 'master.devices.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', + 'master.devices.camera.config.recording': 'Ghi dữ liệu camera', + 'master.devices.camera.config.send': 'Gửi đi', + 'master.devices.camera.config.alarmList': 'Danh sách cảnh báo', + 'master.devices.camera.config.selected': 'đã chọn {0} mục', + 'master.devices.camera.config.clear': 'Xóa', + 'master.devices.camera.config.recordingMode.none': 'Không ghi', + 'master.devices.camera.config.recordingMode.alarm': 'Theo cảnh báo', + 'master.devices.camera.config.recordingMode.all': '24/24', + + // Terminal translations + 'master.devices.terminal.pageTitle': 'Terminal', + 'master.devices.terminal.loadDeviceError': + 'Không thể tải thông tin thiết bị.', + 'master.devices.terminal.mqttError': 'Không thể kết nối MQTT.', + 'master.devices.terminal.genericError': 'Đã có lỗi xảy ra', + 'master.devices.terminal.unsupported.title': 'Thiết bị không hỗ trợ terminal', + 'master.devices.terminal.unsupported.desc': + 'Thiết bị GMSv5 không được hỗ trợ. Vui lòng sử dụng thiết bị khác.', + 'master.devices.terminal.missingChannel.title': + 'Thiếu thông tin kênh điều khiển', + 'master.devices.terminal.missingChannel.desc': + 'Thiết bị chưa được cấu hình ctrl_channel_id nên không thể mở terminal.', + 'master.devices.terminal.missingCredential.title': 'Thiếu thông tin xác thực', + 'master.devices.terminal.missingCredential.desc': + 'Tài khoản hiện tại chưa được cấp frontend_thing_id/frontend_thing_key.', + 'master.devices.terminal.offline': + 'Thiết bị đang ngoại tuyến. Terminal chuyển sang chế độ chỉ xem.', + 'master.devices.terminal.connecting': 'Đang chuẩn bị phiên terminal...', + 'master.devices.terminal.action.clear': 'Xóa màn hình', + 'master.devices.terminal.action.theme': 'Giao diện', }; diff --git a/src/pages/Auth/ForgotPassword/index.tsx b/src/pages/Auth/ForgotPassword/index.tsx index 2e6e44c..fdc7d30 100644 --- a/src/pages/Auth/ForgotPassword/index.tsx +++ b/src/pages/Auth/ForgotPassword/index.tsx @@ -1,11 +1,11 @@ import Footer from '@/components/Footer'; import LangSwitches from '@/components/Lang/LanguageSwitcherAuth'; import ThemeSwitcherAuth from '@/components/Theme/ThemeSwitcherAuth'; -import { THEME_KEY } from '@/constants'; import { ROUTE_LOGIN } from '@/constants/routes'; import { apiUserResetPassword } from '@/services/master/UserController'; import { parseAccessToken } from '@/utils/jwt'; import { getDomainTitle, getLogoImage } from '@/utils/logo'; +import { getTheme } from '@/utils/storage'; import { ProForm, ProFormText } from '@ant-design/pro-components'; import { FormattedMessage, @@ -28,9 +28,7 @@ const ResetPassword = () => { const [tokenValid, setTokenValid] = useState(false); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); - const [isDark, setIsDark] = useState( - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') === 'dark', - ); + const [isDark, setIsDark] = useState(getTheme() === 'dark'); const { token } = theme.useToken(); const [messageApi, contextHolder] = message.useMessage(); const intl = useIntl(); diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx index 75f2704..1b9d403 100644 --- a/src/pages/Auth/index.tsx +++ b/src/pages/Auth/index.tsx @@ -1,7 +1,6 @@ import Footer from '@/components/Footer'; import LangSwitches from '@/components/Lang/LanguageSwitcherAuth'; import ThemeSwitcherAuth from '@/components/Theme/ThemeSwitcherAuth'; -import { THEME_KEY } from '@/constants'; import { ROUTER_HOME } from '@/constants/routes'; import { apiForgotPassword, @@ -14,6 +13,7 @@ import { getDomainTitle, getLogoImage } from '@/utils/logo'; import { getBrowserId, getRefreshToken, + getTheme, removeAccessToken, removeRefreshToken, setAccessToken, @@ -63,9 +63,7 @@ const FormWrapper = ({ }; const LoginPage = () => { - const [isDark, setIsDark] = useState( - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') === 'dark', - ); + const [isDark, setIsDark] = useState(getTheme() === 'dark'); const { token } = theme.useToken(); const [messageApi, contextHolder] = message.useMessage(); const urlParams = new URL(window.location.href).searchParams; @@ -108,8 +106,7 @@ const LoginPage = () => { setInitialState((s: any) => ({ ...s, currentUserProfile: userInfo, - theme: - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') || 'light', + theme: getTheme() as 'light' | 'dark', })); }); } @@ -150,9 +147,7 @@ const LoginPage = () => { setInitialState((s: any) => ({ ...s, currentUserProfile: userInfo, - theme: - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') || - 'light', + theme: getTheme() as 'light' | 'dark', })); }); } @@ -180,9 +175,7 @@ const LoginPage = () => { setInitialState((s: any) => ({ ...s, currentUserProfile: userInfo, - theme: - (localStorage.getItem(THEME_KEY) as 'light' | 'dark') || - 'light', + theme: getTheme() as 'light' | 'dark', })); }); } diff --git a/src/pages/Manager/Device/Camera/components/CameraFormModal.tsx b/src/pages/Manager/Device/Camera/components/CameraFormModal.tsx index 274cc12..0bf1d44 100644 --- a/src/pages/Manager/Device/Camera/components/CameraFormModal.tsx +++ b/src/pages/Manager/Device/Camera/components/CameraFormModal.tsx @@ -77,8 +77,8 @@ const CameraFormModal: React.FC = ({ = ({ footer={[ , , @@ -115,7 +115,7 @@ const CameraFormModal: React.FC = ({ > = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.name.required', + id: 'master.devices.camera.form.name.required', defaultMessage: 'Vui lòng nhập tên', }), }, @@ -131,7 +131,7 @@ const CameraFormModal: React.FC = ({ > @@ -139,7 +139,7 @@ const CameraFormModal: React.FC = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.type.required', + id: 'master.devices.camera.form.type.required', defaultMessage: 'Vui lòng chọn loại', }), }, @@ -158,7 +158,7 @@ const CameraFormModal: React.FC = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.username.required', + id: 'master.devices.camera.form.username.required', defaultMessage: 'Vui lòng nhập tài khoản', }), }, @@ -174,7 +174,7 @@ const CameraFormModal: React.FC = ({ > = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.password.required', + id: 'master.devices.camera.form.password.required', defaultMessage: 'Vui lòng nhập mật khẩu', }), }, @@ -199,7 +199,7 @@ const CameraFormModal: React.FC = ({ > = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.ip.required', + id: 'master.devices.camera.form.ip.required', defaultMessage: 'Vui lòng nhập địa chỉ IP', }), }, @@ -224,7 +224,7 @@ const CameraFormModal: React.FC = ({ > @@ -234,7 +234,7 @@ const CameraFormModal: React.FC = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.rtspPort.required', + id: 'master.devices.camera.form.rtspPort.required', defaultMessage: 'Vui lòng nhập cổng RTSP', }), }, @@ -254,7 +254,7 @@ const CameraFormModal: React.FC = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.httpPort.required', + id: 'master.devices.camera.form.httpPort.required', defaultMessage: 'Vui lòng nhập cổng HTTP', }), }, @@ -277,7 +277,7 @@ const CameraFormModal: React.FC = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.stream.required', + id: 'master.devices.camera.form.stream.required', defaultMessage: 'Vui lòng nhập luồng', }), }, @@ -297,7 +297,7 @@ const CameraFormModal: React.FC = ({ = ({ { required: true, message: intl.formatMessage({ - id: 'master.camera.form.channel.required', + id: 'master.devices.camera.form.channel.required', defaultMessage: 'Vui lòng nhập kênh', }), }, diff --git a/src/pages/Manager/Device/Camera/components/CameraTable.tsx b/src/pages/Manager/Device/Camera/components/CameraTable.tsx index 1daaaff..8ea6cdc 100644 --- a/src/pages/Manager/Device/Camera/components/CameraTable.tsx +++ b/src/pages/Manager/Device/Camera/components/CameraTable.tsx @@ -51,7 +51,7 @@ const CameraTable: React.FC = ({ const columns = [ { title: intl.formatMessage({ - id: 'master.camera.table.column.name', + id: 'master.devices.camera.table.column.name', defaultMessage: 'Tên', }), dataIndex: 'name', @@ -62,7 +62,7 @@ const CameraTable: React.FC = ({ }, { title: intl.formatMessage({ - id: 'master.camera.table.column.type', + id: 'master.devices.camera.table.column.type', defaultMessage: 'Loại', }), dataIndex: 'cate_id', @@ -71,7 +71,7 @@ const CameraTable: React.FC = ({ }, { title: intl.formatMessage({ - id: 'master.camera.table.column.ip', + id: 'master.devices.camera.table.column.ip', defaultMessage: 'Địa chỉ IP', }), dataIndex: 'ip', @@ -80,7 +80,7 @@ const CameraTable: React.FC = ({ }, { title: intl.formatMessage({ - id: 'master.camera.table.column.action', + id: 'master.devices.camera.table.column.action', defaultMessage: 'Thao tác', }), key: 'action', @@ -89,7 +89,7 @@ const CameraTable: React.FC = ({ title={ !isOnline ? intl.formatMessage({ - id: 'master.camera.table.offline.tooltip', + id: 'master.devices.camera.table.offline.tooltip', defaultMessage: 'Thiết bị đang ngoại tuyến', }) : '' @@ -113,7 +113,7 @@ const CameraTable: React.FC = ({ title={ !isOnline ? intl.formatMessage({ - id: 'master.camera.table.offline.tooltip', + id: 'master.devices.camera.table.offline.tooltip', defaultMessage: 'Thiết bị đang ngoại tuyến', }) : '' @@ -126,7 +126,7 @@ const CameraTable: React.FC = ({ disabled={!isOnline} > {intl.formatMessage({ - id: 'master.camera.table.add', + id: 'master.devices.camera.table.add', defaultMessage: 'Tạo mới camera', })} @@ -140,7 +140,7 @@ const CameraTable: React.FC = ({ title={ !isOnline ? intl.formatMessage({ - id: 'master.camera.table.offline.tooltip', + id: 'master.devices.camera.table.offline.tooltip', defaultMessage: 'Thiết bị đang ngoại tuyến', }) : '' @@ -172,7 +172,7 @@ const CameraTable: React.FC = ({ showTotal: (total: number, range: [number, number]) => intl.formatMessage( { - id: 'master.camera.table.pagination', + id: 'master.devices.camera.table.pagination', defaultMessage: 'Hiển thị {0}-{1} của {2} camera', }, { diff --git a/src/pages/Manager/Device/Camera/components/ConfigCameraV5.tsx b/src/pages/Manager/Device/Camera/components/ConfigCameraV5.tsx index ce21ac6..ef5fc52 100644 --- a/src/pages/Manager/Device/Camera/components/ConfigCameraV5.tsx +++ b/src/pages/Manager/Device/Camera/components/ConfigCameraV5.tsx @@ -21,14 +21,14 @@ const CameraV5: React.FC = ({ () => [ { label: intl.formatMessage({ - id: 'master.camera.config.recordingMode.none', + id: 'master.devices.camera.config.recordingMode.none', defaultMessage: 'Không ghi', }), value: 'none', }, { label: intl.formatMessage({ - id: 'master.camera.config.recordingMode.all', + id: 'master.devices.camera.config.recordingMode.all', defaultMessage: '24/24', }), value: 'all', @@ -52,7 +52,7 @@ const CameraV5: React.FC = ({
{intl.formatMessage({ - id: 'master.camera.config.recording', + id: 'master.devices.camera.config.recording', defaultMessage: 'Ghi dữ liệu camera', })} @@ -66,7 +66,7 @@ const CameraV5: React.FC = ({
diff --git a/src/pages/Manager/Device/Camera/components/ConfigCameraV6.tsx b/src/pages/Manager/Device/Camera/components/ConfigCameraV6.tsx index a5d2351..3bc0a8f 100644 --- a/src/pages/Manager/Device/Camera/components/ConfigCameraV6.tsx +++ b/src/pages/Manager/Device/Camera/components/ConfigCameraV6.tsx @@ -42,21 +42,21 @@ const CameraV6: React.FC = ({ () => [ { label: intl.formatMessage({ - id: 'master.camera.config.recordingMode.none', + id: 'master.devices.camera.config.recordingMode.none', defaultMessage: 'Không ghi', }), value: 'none', }, { label: intl.formatMessage({ - id: 'master.camera.config.recordingMode.alarm', + id: 'master.devices.camera.config.recordingMode.alarm', defaultMessage: 'Theo cảnh báo', }), value: 'alarm', }, { label: intl.formatMessage({ - id: 'master.camera.config.recordingMode.all', + id: 'master.devices.camera.config.recordingMode.all', defaultMessage: '24/24', }), value: 'all', @@ -148,7 +148,7 @@ const CameraV6: React.FC = ({
{intl.formatMessage({ - id: 'master.camera.config.recording', + id: 'master.devices.camera.config.recording', defaultMessage: 'Ghi dữ liệu camera', })} @@ -164,7 +164,7 @@ const CameraV6: React.FC = ({ title={ !isOnline ? intl.formatMessage({ - id: 'master.camera.table.offline.tooltip', + id: 'master.devices.camera.table.offline.tooltip', defaultMessage: 'Thiết bị đang ngoại tuyến', }) : '' @@ -176,7 +176,7 @@ const CameraV6: React.FC = ({ disabled={!isOnline} > {intl.formatMessage({ - id: 'master.camera.config.send', + id: 'master.devices.camera.config.send', defaultMessage: 'Gửi đi', })} @@ -189,7 +189,7 @@ const CameraV6: React.FC = ({
{intl.formatMessage({ - id: 'master.camera.config.alarmList', + id: 'master.devices.camera.config.alarmList', defaultMessage: 'Danh sách cảnh báo', })} @@ -204,7 +204,7 @@ const CameraV6: React.FC = ({ {intl.formatMessage( { - id: 'master.camera.config.selected', + id: 'master.devices.camera.config.selected', defaultMessage: 'đã chọn {0} mục', }, { @@ -214,7 +214,7 @@ const CameraV6: React.FC = ({ diff --git a/src/pages/Manager/Device/Camera/index.tsx b/src/pages/Manager/Device/Camera/index.tsx index 9ffc971..bb3a26a 100644 --- a/src/pages/Manager/Device/Camera/index.tsx +++ b/src/pages/Manager/Device/Camera/index.tsx @@ -146,7 +146,7 @@ const CameraConfigPage = () => { if (!isOnline) { message.error( intl.formatMessage({ - id: 'master.camera.config.error.deviceOffline', + id: 'master.devices.camera.config.error.deviceOffline', defaultMessage: 'Thiết bị đang ngoại tuyến, không thể gửi cấu hình', }), ); @@ -156,7 +156,7 @@ const CameraConfigPage = () => { if (!thing?.metadata?.cfg_channel_id || !thing?.metadata?.external_id) { message.error( intl.formatMessage({ - id: 'master.camera.config.error.missingConfig', + id: 'master.devices.camera.config.error.missingConfig', defaultMessage: 'Thiếu thông tin cấu hình thiết bị', }), ); @@ -188,7 +188,7 @@ const CameraConfigPage = () => { mqttClient.publish(pubTopic, payload); message.success( intl.formatMessage({ - id: 'master.camera.config.success', + id: 'master.devices.camera.config.success', defaultMessage: 'Đã gửi cấu hình thành công', }), ); @@ -196,7 +196,7 @@ const CameraConfigPage = () => { } else { message.error( intl.formatMessage({ - id: 'master.camera.config.error.mqttNotConnected', + id: 'master.devices.camera.config.error.mqttNotConnected', defaultMessage: 'MQTT chưa kết nối', }), ); @@ -290,7 +290,7 @@ const CameraConfigPage = () => { {thing?.name || intl.formatMessage({ - id: 'master.camera.loading', + id: 'master.devices.camera.loading', defaultMessage: 'Loading...', })} diff --git a/src/pages/Manager/Device/Terminal/index.tsx b/src/pages/Manager/Device/Terminal/index.tsx index 28672fd..2febe6f 100644 --- a/src/pages/Manager/Device/Terminal/index.tsx +++ b/src/pages/Manager/Device/Terminal/index.tsx @@ -5,6 +5,7 @@ import { getBadgeConnection } from '@/components/shared/ThingShared'; import { ROUTE_MANAGER_DEVICES } from '@/constants/routes'; import { apiGetThingDetail } from '@/services/master/ThingController'; import { mqttClient } from '@/utils/mqttClient'; +import { getTerminalTheme, setTerminalTheme } from '@/utils/storage'; import { BgColorsOutlined, ClearOutlined } from '@ant-design/icons'; import { PageContainer, ProCard } from '@ant-design/pro-components'; import { history, useIntl, useModel, useParams } from '@umijs/max'; @@ -210,7 +211,7 @@ const DeviceTerminalPage = () => { console.error('Failed to load device details', error); setTerminalError( intl.formatMessage({ - id: 'terminal.loadDeviceError', + id: 'master.devices.terminal.loadDeviceError', defaultMessage: 'Không thể tải thông tin thiết bị.', }), ); @@ -231,7 +232,7 @@ const DeviceTerminalPage = () => { // Khôi phục theme từ localStorage khi component mount useEffect(() => { - const savedTheme = localStorage.getItem('terminal_theme_key'); + const savedTheme = getTerminalTheme(); if (savedTheme && TERMINAL_THEMES[savedTheme]) { setSelectedThemeKey(savedTheme); } @@ -326,7 +327,7 @@ const DeviceTerminalPage = () => { mqttClient.disconnect(); setTerminalError( intl.formatMessage({ - id: 'terminal.mqttError', + id: 'master.devices.terminal.mqttError', defaultMessage: 'Không thể kết nối MQTT.', }), ); @@ -428,7 +429,7 @@ const DeviceTerminalPage = () => { const pageTitle = thing?.name || intl.formatMessage({ - id: 'terminal.pageTitle', + id: 'master.devices.terminal.pageTitle', defaultMessage: 'Terminal', }); @@ -456,12 +457,12 @@ const DeviceTerminalPage = () => { /** * Xử lý thay đổi theme * - Cập nhật state - * - Lưu vào localStorage + * - Lưu vào localStorage thông qua storage utility */ const handleThemeChange: MenuProps['onClick'] = (e) => { const themeKey = e.key; setSelectedThemeKey(themeKey); - localStorage.setItem('terminal_theme_key', themeKey); + setTerminalTheme(themeKey); }; /** @@ -507,7 +508,7 @@ const DeviceTerminalPage = () => { { renderBlockingResult( 'info', intl.formatMessage({ - id: 'terminal.unsupported.title', + id: 'master.devices.terminal.unsupported.title', defaultMessage: 'Thiết bị không hỗ trợ terminal', }), intl.formatMessage({ - id: 'terminal.unsupported.desc', + id: 'master.devices.terminal.unsupported.desc', defaultMessage: 'Thiết bị GMSv5 không được hỗ trợ. Vui lòng sử dụng thiết bị khác.', }), @@ -549,11 +550,11 @@ const DeviceTerminalPage = () => { renderBlockingResult( 'warning', intl.formatMessage({ - id: 'terminal.missingChannel.title', + id: 'master.devices.terminal.missingChannel.title', defaultMessage: 'Thiếu thông tin kênh điều khiển', }), intl.formatMessage({ - id: 'terminal.missingChannel.desc', + id: 'master.devices.terminal.missingChannel.desc', defaultMessage: 'Thiết bị chưa được cấu hình ctrl_channel_id nên không thể mở terminal.', }), @@ -569,11 +570,11 @@ const DeviceTerminalPage = () => { renderBlockingResult( 'warning', intl.formatMessage({ - id: 'terminal.missingCredential.title', + id: 'master.devices.terminal.missingCredential.title', defaultMessage: 'Thiếu thông tin xác thực', }), intl.formatMessage({ - id: 'terminal.missingCredential.desc', + id: 'master.devices.terminal.missingCredential.desc', defaultMessage: 'Tài khoản hiện tại chưa được cấp frontend_thing_id/frontend_thing_key.', }), @@ -588,7 +589,7 @@ const DeviceTerminalPage = () => { type="warning" showIcon message={intl.formatMessage({ - id: 'terminal.offline', + id: 'master.devices.terminal.offline', defaultMessage: 'Thiết bị đang ngoại tuyến. Terminal chuyển sang chế độ chỉ xem.', })} @@ -600,8 +601,9 @@ const DeviceTerminalPage = () => { type="info" showIcon message={intl.formatMessage({ - id: 'terminal.connecting', - defaultMessage: 'Đang chuẩn bị phiên terminal...', + id: 'master.devices.terminal.connecting', + defaultMessage: + 'Đang chuẩn bị phiên master.devices.terminal...', })} /> )} @@ -631,7 +633,7 @@ const DeviceTerminalPage = () => { disabled={!isSessionReady} > {intl.formatMessage({ - id: 'terminal.action.clear', + id: 'master.devices.terminal.action.clear', defaultMessage: 'Xóa màn hình', })} @@ -645,7 +647,7 @@ const DeviceTerminalPage = () => { >