277 lines
8.2 KiB
TypeScript
277 lines
8.2 KiB
TypeScript
import { apiQueryConfigAlarm } from '@/services/master/MessageController';
|
|
import { useIntl, useModel } from '@umijs/max';
|
|
import {
|
|
Button,
|
|
Card,
|
|
Col,
|
|
Row,
|
|
Select,
|
|
theme,
|
|
Tooltip,
|
|
Typography,
|
|
} from 'antd';
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
|
const { Text } = Typography;
|
|
|
|
interface CameraV6Props {
|
|
thing: MasterModel.Thing | null;
|
|
cameraConfig?: MasterModel.CameraV6 | null;
|
|
onSubmit?: (config: MasterModel.CameraV6) => void;
|
|
isOnline?: boolean;
|
|
}
|
|
|
|
const CameraV6: React.FC<CameraV6Props> = ({
|
|
thing,
|
|
cameraConfig,
|
|
onSubmit,
|
|
isOnline = false,
|
|
}) => {
|
|
const { token } = theme.useToken();
|
|
const intl = useIntl();
|
|
const { initialState } = useModel('@@initialState');
|
|
const [selectedAlerts, setSelectedAlerts] = useState<string[]>([]);
|
|
const [recordingMode, setRecordingMode] =
|
|
useState<MasterModel.CameraRecordType>('none');
|
|
const [alarmConfig, setAlarmConfig] = useState<MasterModel.Alarm[] | null>(
|
|
null,
|
|
);
|
|
|
|
// Recording modes for V6 - using useMemo for i18n
|
|
const RECORDING_MODES = useMemo(
|
|
() => [
|
|
{
|
|
label: intl.formatMessage({
|
|
id: 'master.devices.camera.config.recordingMode.none',
|
|
defaultMessage: 'Không ghi',
|
|
}),
|
|
value: 'none',
|
|
},
|
|
{
|
|
label: intl.formatMessage({
|
|
id: 'master.devices.camera.config.recordingMode.alarm',
|
|
defaultMessage: 'Theo cảnh báo',
|
|
}),
|
|
value: 'alarm',
|
|
},
|
|
{
|
|
label: intl.formatMessage({
|
|
id: 'master.devices.camera.config.recordingMode.all',
|
|
defaultMessage: '24/24',
|
|
}),
|
|
value: 'all',
|
|
},
|
|
],
|
|
[intl],
|
|
);
|
|
|
|
// Initialize states from cameraConfig when it's available
|
|
useEffect(() => {
|
|
if (cameraConfig) {
|
|
// Set recording mode from config
|
|
if (cameraConfig.record_type) {
|
|
setRecordingMode(cameraConfig.record_type);
|
|
}
|
|
|
|
// Set selected alerts from config
|
|
if (
|
|
cameraConfig.record_alarm_list &&
|
|
Array.isArray(cameraConfig.record_alarm_list)
|
|
) {
|
|
setSelectedAlerts(cameraConfig.record_alarm_list);
|
|
}
|
|
}
|
|
}, [cameraConfig]);
|
|
|
|
// Fetch alarm config when thing data is available and recording mode is 'alarm'
|
|
useEffect(() => {
|
|
const fetchAlarmConfig = async () => {
|
|
if (
|
|
!thing ||
|
|
!initialState?.currentUserProfile?.metadata?.frontend_thing_key ||
|
|
recordingMode !== 'alarm'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const resp = await apiQueryConfigAlarm(
|
|
thing.metadata?.data_channel_id || '',
|
|
initialState.currentUserProfile.metadata.frontend_thing_key,
|
|
{
|
|
offset: 0,
|
|
limit: 1,
|
|
subtopic: `config.${thing.metadata?.type}.alarms`,
|
|
},
|
|
);
|
|
if (resp.messages && resp.messages.length > 0) {
|
|
const parsed = resp.messages[0].string_value_parsed;
|
|
if (Array.isArray(parsed)) {
|
|
setAlarmConfig(parsed as MasterModel.Alarm[]);
|
|
} else {
|
|
setAlarmConfig([]);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch alarm config:', error);
|
|
}
|
|
};
|
|
|
|
fetchAlarmConfig();
|
|
}, [thing, initialState, recordingMode]);
|
|
|
|
const handleAlertToggle = (alertId: string) => {
|
|
if (selectedAlerts.includes(alertId)) {
|
|
setSelectedAlerts(selectedAlerts.filter((id) => id !== alertId));
|
|
} else {
|
|
setSelectedAlerts([...selectedAlerts, alertId]);
|
|
}
|
|
};
|
|
|
|
const handleClearAlerts = () => {
|
|
setSelectedAlerts([]);
|
|
};
|
|
|
|
const handleSubmitConfig = () => {
|
|
onSubmit?.({
|
|
...cameraConfig,
|
|
record_type: recordingMode,
|
|
record_alarm_list: recordingMode === 'alarm' ? selectedAlerts : [],
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Card className="p-4">
|
|
{/* Recording Mode */}
|
|
<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.devices.camera.config.recording',
|
|
defaultMessage: 'Ghi dữ liệu camera',
|
|
})}
|
|
</Text>
|
|
<Select
|
|
value={recordingMode}
|
|
onChange={setRecordingMode}
|
|
options={RECORDING_MODES}
|
|
className="w-full"
|
|
popupMatchSelectWidth={false}
|
|
/>
|
|
</div>
|
|
<Tooltip
|
|
title={
|
|
!isOnline
|
|
? intl.formatMessage({
|
|
id: 'master.devices.camera.table.offline.tooltip',
|
|
defaultMessage: 'Thiết bị đang ngoại tuyến',
|
|
})
|
|
: ''
|
|
}
|
|
>
|
|
<Button
|
|
type="primary"
|
|
onClick={handleSubmitConfig}
|
|
disabled={!isOnline}
|
|
>
|
|
{intl.formatMessage({
|
|
id: 'master.devices.camera.config.send',
|
|
defaultMessage: 'Gửi đi',
|
|
})}
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Alert List - Only show when mode is 'alarm' */}
|
|
{recordingMode === 'alarm' && (
|
|
<div>
|
|
<Text strong className="block mb-2">
|
|
{intl.formatMessage({
|
|
id: 'master.devices.camera.config.alarmList',
|
|
defaultMessage: 'Danh sách cảnh báo',
|
|
})}
|
|
</Text>
|
|
|
|
<div
|
|
className="flex justify-between items-center mb-4 px-3 py-2 rounded border"
|
|
style={{
|
|
background: token.colorBgContainer,
|
|
borderColor: token.colorBorder,
|
|
}}
|
|
>
|
|
<Text type="secondary">
|
|
{intl.formatMessage(
|
|
{
|
|
id: 'master.devices.camera.config.selected',
|
|
defaultMessage: 'đã chọn {0} mục',
|
|
},
|
|
{
|
|
0: selectedAlerts.length,
|
|
},
|
|
)}
|
|
</Text>
|
|
<Button type="link" onClick={handleClearAlerts}>
|
|
{intl.formatMessage({
|
|
id: 'master.devices.camera.config.clear',
|
|
defaultMessage: 'Xóa',
|
|
})}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Alert Cards Grid */}
|
|
<Row gutter={[12, 12]}>
|
|
{alarmConfig?.map((alarm) => {
|
|
const alarmId = alarm.id ?? '';
|
|
const isSelected =
|
|
alarmId !== '' && selectedAlerts.includes(alarmId);
|
|
return (
|
|
<Col xs={12} sm={8} md={6} lg={4} xl={4} key={alarmId}>
|
|
<Card
|
|
size="small"
|
|
hoverable
|
|
onClick={() => handleAlertToggle(alarmId)}
|
|
className="cursor-pointer h-24 flex items-center justify-center"
|
|
style={{
|
|
borderColor: isSelected
|
|
? token.colorPrimary
|
|
: token.colorBorder,
|
|
borderWidth: isSelected ? 2 : 1,
|
|
background: isSelected
|
|
? token.colorPrimaryBg
|
|
: token.colorBgContainer,
|
|
}}
|
|
>
|
|
<div className="p-1 text-center w-full flex items-center justify-center h-full">
|
|
<Text
|
|
className="text-xs"
|
|
style={{
|
|
color: isSelected
|
|
? token.colorPrimary
|
|
: token.colorText,
|
|
display: '-webkit-box',
|
|
WebkitLineClamp: 3,
|
|
WebkitBoxOrient: 'vertical',
|
|
overflow: 'hidden',
|
|
wordBreak: 'break-word',
|
|
lineHeight: '1.2em',
|
|
}}
|
|
title={alarm.name}
|
|
>
|
|
{alarm.name}
|
|
</Text>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
);
|
|
})}
|
|
</Row>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default CameraV6;
|