import React, { useEffect, useState, useCallback } from 'react';
import { Row, Col, Card, Tag, Select, Statistic, Modal, Spin, Badge, Tooltip, DatePicker, Alert, Button } from 'antd';
import { HomeOutlined, UserOutlined, CalendarOutlined, BankOutlined, HistoryOutlined, ShopOutlined } from '@ant-design/icons';
import dayjs, { Dayjs } from 'dayjs';
import api from '../../api';
function getCardStyle(room: any): React.CSSProperties {
let base: React.CSSProperties;
if (room.status === 'maintenance') base = { background: '#f5f5f5', borderColor: '#d9d9d9' };
else if (room.currentCount === 0) base = { background: '#f6ffed', borderColor: '#b7eb8f' };
else if (room.currentCount >= room.capacity) base = { background: '#fff2f0', borderColor: '#ffccc7' };
else base = { background: '#e6f4ff', borderColor: '#91caff' };
if (room.tenantColor) {
return { ...base, background: `color-mix(in srgb, ${room.tenantColor} 15%, ${base.background || '#fff'} 85%)` };
}
return base;
}
function getStatusLabel(room: any) {
if (room.status === 'maintenance') return 维修中;
if (room.currentCount === 0) return 空闲;
if (room.currentCount >= room.capacity) return 满员;
return 部分入住;
}
function getTenantTags(occupants: any[]) {
const tenantList = [
...new Map(
occupants
.filter((o: any) => o.tenantName)
.map((o: any) => [o.tenantId, { name: o.tenantName, color: o.tenantColor }]),
).values(),
] as { name: string; color: string | null }[];
if (tenantList.length === 0) return null;
return (
{tenantList.map((t) => (
}
>
{t.name}
))}
);
}
const RoomVisualPage: React.FC = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [selectedBuilding, setSelectedBuilding] = useState('all');
const [selectedTenant, setSelectedTenant] = useState('all');
const [detailRoom, setDetailRoom] = useState(null);
const [asOf, setAsOf] = useState(null);
const isHistorical = !!asOf && !asOf.isSame(dayjs(), 'day');
const fetchData = useCallback(async () => {
setLoading(true);
try {
const params = isHistorical ? { asOf: asOf!.format('YYYY-MM-DD') } : undefined;
const res: any = await api.get('/rooms/visual', { params });
setData(res);
} catch (e) {
console.error(e);
}
setLoading(false);
}, [isHistorical, asOf]);
useEffect(() => {
fetchData();
}, [fetchData]);
if (!data) return ;
const rooms = data.rooms.filter((r: any) => {
if (selectedBuilding !== 'all' && r.building !== selectedBuilding) return false;
if (selectedTenant !== 'all' && !(r.tenantIds || []).includes(selectedTenant)) return false;
return true;
});
const totalRooms = rooms.length;
const emptyRooms = rooms.filter(
(r: any) => r.currentCount === 0 && r.status !== 'maintenance',
).length;
const availableBeds = rooms.reduce(
(sum: number, r: any) =>
r.status !== 'maintenance' ? sum + (r.capacity - r.currentCount) : sum,
0,
);
const fullRooms = rooms.filter((r: any) => r.currentCount >= r.capacity).length;
/* getCardStyle, getStatusLabel, getTenantTags are now standalone functions outside the component */
return (
宿舍总览
}
disabledDate={(d) => d && d.isAfter(dayjs(), 'day')}
style={{ width: 180 }}
/>
{isHistorical && (
}
style={{ marginBottom: 16 }}
title={`正在查看 ${asOf!.format('YYYY年M月D日')} 的历史入住情况(含当日已归档房间),非实时数据`}
action={
}
/>
)}
{loading &&
}
{/* 统计栏 */}
} />
{/* 房态网格 */}
{rooms.map((room: any) => (
setDetailRoom(room)}
>
{room.tenantColor && (
)}
{room.roomNumber}
{getStatusLabel(room)}
{room.building && {room.building} }
{room.floor && {room.floor}F}
= room.capacity
? '#FF3B30'
: room.currentCount > 0
? '#007AFF'
: '#34C759',
fontSize: 12,
}}
/>
{room.orgLabel && (
}>
{room.orgLabel}
)}
{getTenantTags(room.occupants)}
{room.occupants.length > 0 && (
{room.occupants.slice(0, 4).map((o: any) => (
}>
{o.studentName}
))}
{room.occupants.length > 4 && +{room.occupants.length - 4}}
)}
))}
{/* 详情弹窗 */}
setDetailRoom(null)}
footer={null}
width={500}
>
{detailRoom && (
位置:{detailRoom.building || '-'} {detailRoom.floor ? `${detailRoom.floor}F` : ''}
{detailRoom.tenantColor && (
{detailRoom.occupants[0]?.tenantName || '租户'}
)}
{getStatusLabel(detailRoom)}
{detailRoom.occupants.length > 0 ? (
当前住户
{detailRoom.occupants.map((o: any) => (
{o.studentName}
{o.organization && (
{o.organization}
)}
{o.days} 天
入住:{o.checkInDate} | 计费起:{o.billingStartDate}
{o.supervisor && (
负责人:{o.supervisor}
)}
))}
) : (
当前无住户,可安排入住
)}
)}
);
};
export default RoomVisualPage;