feat: add Manager Dashboard and Device Terminal features

This commit is contained in:
2026-02-09 16:38:28 +07:00
parent 4af34eab3e
commit 674d53bcc5
12 changed files with 1475 additions and 29 deletions

View File

@@ -0,0 +1,259 @@
import IconFont from '@/components/IconFont';
import { apiQueryGroups } from '@/services/master/GroupController';
import { apiQueryLogs } from '@/services/master/LogController';
import { apiSearchThings } from '@/services/master/ThingController';
import { apiQueryUsers } from '@/services/master/UserController';
import { RightOutlined } from '@ant-design/icons';
import { PageContainer, ProCard } from '@ant-design/pro-components';
import { history, useIntl } from '@umijs/max';
import { Button, Col, Divider, Row, Statistic, Typography } from 'antd';
import { useEffect, useState } from 'react';
import CountUp from 'react-countup';
const { Text } = Typography;
const ManagerDashboard = () => {
const intl = useIntl();
const [counts, setCounts] = useState({
devices: 0,
groups: 0,
users: 0,
logs: 0,
});
const [deviceMetadata, setDeviceMetadata] =
useState<MasterModel.ThingsResponseMetadata | null>(null);
const formatter = (value: number | string) => (
<CountUp end={Number(value)} separator="," duration={2} />
);
useEffect(() => {
const fetchData = async () => {
try {
const [devicesRes, groupsRes, usersRes, logsRes] = await Promise.all([
apiSearchThings({ limit: 1 }),
apiQueryGroups({}),
apiQueryUsers({ limit: 1 }),
apiQueryLogs({ limit: 1 }, 'user_logs'),
]);
const devicesTotal = devicesRes?.total || 0;
const metadata = (devicesRes as any)?.metadata || null;
// Group response handling
const groupsTotal =
(Array.isArray(groupsRes)
? groupsRes.length
: (groupsRes as any)?.total) || 0;
const usersTotal = usersRes?.total || 0;
const logsTotal = logsRes?.total || 0;
setCounts({
devices: devicesTotal,
groups: groupsTotal,
users: usersTotal,
logs: logsTotal,
});
setDeviceMetadata(metadata);
} catch (error) {
console.error('Failed to fetch dashboard counts:', error);
}
};
fetchData();
}, []);
return (
<PageContainer>
<ProCard gutter={[16, 16]} ghost>
<ProCard colSpan={{ xs: 24, md: 10 }} ghost gutter={[16, 16]} wrap>
<ProCard
colSpan={24}
title={intl.formatMessage({
id: 'menu.manager.devices',
defaultMessage: 'Thiết bị',
})}
extra={
<Button
type="link"
onClick={() => history.push('/manager/devices')}
icon={<RightOutlined />}
>
Xem chi tiết
</Button>
}
>
<Statistic
value={counts.devices}
formatter={formatter as any}
valueStyle={{ color: '#1890ff' }}
prefix={
<IconFont
type="icon-gateway"
style={{ fontSize: 24, marginRight: 8 }}
/>
}
/>
</ProCard>
<ProCard
colSpan={24}
title={intl.formatMessage({
id: 'menu.manager.groups',
defaultMessage: 'Đơn vị',
})}
extra={
<Button
type="link"
onClick={() => history.push('/manager/groups')}
icon={<RightOutlined />}
>
Xem chi tiết
</Button>
}
>
<Statistic
value={counts.groups}
formatter={formatter as any}
valueStyle={{ color: '#52c41a' }}
prefix={
<IconFont
type="icon-tree"
style={{ fontSize: 24, marginRight: 8 }}
/>
}
/>
</ProCard>
<ProCard
colSpan={24}
title={intl.formatMessage({
id: 'menu.manager.users',
defaultMessage: 'Người dùng',
})}
extra={
<Button
type="link"
onClick={() => history.push('/manager/users')}
icon={<RightOutlined />}
>
Xem chi tiết
</Button>
}
>
<Statistic
value={counts.users}
formatter={formatter as any}
valueStyle={{ color: '#faad14' }}
prefix={
<IconFont
type="icon-users"
style={{ fontSize: 24, marginRight: 8 }}
/>
}
/>
</ProCard>
<ProCard
colSpan={24}
title={intl.formatMessage({
id: 'menu.manager.logs',
defaultMessage: 'Hoạt động',
})}
extra={
<Button
type="link"
onClick={() => history.push('/manager/logs')}
icon={<RightOutlined />}
>
Xem chi tiết
</Button>
}
>
<Statistic
value={counts.logs}
formatter={formatter as any}
valueStyle={{ color: '#f5222d' }}
prefix={
<IconFont
type="icon-diary"
style={{ fontSize: 24, marginRight: 8 }}
/>
}
/>
</ProCard>
</ProCard>
<ProCard
colSpan={{ xs: 24, md: 8 }}
title="Trạng thái thiết bị"
headerBordered
>
{deviceMetadata && (
<Row gutter={[0, 16]}>
<Col span={24}>
<Text type="secondary">Kết nối</Text>
<div style={{ fontSize: 16, fontWeight: 'bold' }}>
<CountUp end={deviceMetadata.total_connected || 0} /> /{' '}
{deviceMetadata.total_thing}
</div>
</Col>
<Divider style={{ margin: 0 }} />
<Col span={24}>
<Text type="secondary">SOS</Text>
<div
style={{
fontSize: 16,
fontWeight: 'bold',
color:
(deviceMetadata.total_sos || 0) > 0
? '#ff4d4f'
: 'inherit',
}}
>
<CountUp end={deviceMetadata.total_sos || 0} />
</div>
</Col>
<Divider style={{ margin: 0 }} />
<Col span={24}>
<Text type="secondary">Bình thường</Text>
<div
style={{
fontSize: 16,
fontWeight: 'bold',
color: '#52c41a',
}}
>
<CountUp end={deviceMetadata.total_state_level_0 || 0} />
</div>
</Col>
<Divider style={{ margin: 0 }} />
<Col span={24}>
<Text type="secondary">Cảnh báo</Text>
<div
style={{
fontSize: 16,
fontWeight: 'bold',
color: '#faad14',
}}
>
<CountUp end={deviceMetadata.total_state_level_1 || 0} />
</div>
</Col>
<Divider style={{ margin: 0 }} />
<Col span={24}>
<Text type="secondary">Nghiêm trọng</Text>
<div
style={{
fontSize: 16,
fontWeight: 'bold',
color: '#f5222d',
}}
>
<CountUp end={deviceMetadata.total_state_level_2 || 0} />
</div>
</Col>
</Row>
)}
</ProCard>
</ProCard>
</PageContainer>
);
};
export default ManagerDashboard;