forked from wangziqi/gongxue-base
feat: add bed occupancy stats to RoomVisual cards and API
This commit is contained in:
@@ -86,11 +86,9 @@ const RoomVisualPage: React.FC = () => {
|
|||||||
const emptyRooms = rooms.filter(
|
const emptyRooms = rooms.filter(
|
||||||
(r: any) => r.currentCount === 0 && r.status !== 'maintenance',
|
(r: any) => r.currentCount === 0 && r.status !== 'maintenance',
|
||||||
).length;
|
).length;
|
||||||
const availableBeds = rooms.reduce(
|
const totalBeds = rooms.reduce((sum: number, r: any) => sum + (r.totalBeds || 0), 0);
|
||||||
(sum: number, r: any) =>
|
const occupiedBeds = rooms.reduce((sum: number, r: any) => sum + (r.occupiedBeds || 0), 0);
|
||||||
r.status !== 'maintenance' ? sum + (r.capacity - r.currentCount) : sum,
|
const availableBedsCount = totalBeds - occupiedBeds;
|
||||||
0,
|
|
||||||
);
|
|
||||||
const fullRooms = rooms.filter((r: any) => r.currentCount >= r.capacity).length;
|
const fullRooms = rooms.filter((r: any) => r.currentCount >= r.capacity).length;
|
||||||
|
|
||||||
/* getCardStyle, getStatusLabel, getTenantTags are now standalone functions outside the component */
|
/* getCardStyle, getStatusLabel, getTenantTags are now standalone functions outside the component */
|
||||||
@@ -180,7 +178,7 @@ const RoomVisualPage: React.FC = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} sm={6}>
|
<Col xs={12} sm={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="可安排床位" value={availableBeds} styles={{ value: { color: '#007AFF' } }} />
|
<Statistic title="可安排床位" value={availableBedsCount} styles={{ value: { color: '#007AFF' } }} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} sm={6}>
|
<Col xs={12} sm={6}>
|
||||||
@@ -230,6 +228,11 @@ const RoomVisualPage: React.FC = () => {
|
|||||||
{room.building && <span>{room.building} </span>}
|
{room.building && <span>{room.building} </span>}
|
||||||
{room.floor && <span>{room.floor}F</span>}
|
{room.floor && <span>{room.floor}F</span>}
|
||||||
</div>
|
</div>
|
||||||
|
{room.totalBeds > 0 && (
|
||||||
|
<div style={{ fontSize: 12, color: room.occupiedBeds >= room.totalBeds ? '#FF3B30' : '#34C759', marginBottom: 6 }}>
|
||||||
|
床位: {room.occupiedBeds}/{room.totalBeds}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 8 }}>
|
||||||
<Badge
|
<Badge
|
||||||
count={`${room.currentCount}/${room.capacity}`}
|
count={`${room.currentCount}/${room.capacity}`}
|
||||||
|
|||||||
@@ -235,25 +235,30 @@ export class RoomsService {
|
|||||||
? rooms.filter((r) => r.status !== 'archived' || (occMap.get(r.id)?.length ?? 0) > 0)
|
? rooms.filter((r) => r.status !== 'archived' || (occMap.get(r.id)?.length ?? 0) > 0)
|
||||||
: rooms;
|
: rooms;
|
||||||
|
|
||||||
|
// 批量获取床位统计
|
||||||
|
const allBeds = await this.bedRepo.find({
|
||||||
|
where: { roomId: In(visibleRooms.map((r) => r.id)) },
|
||||||
|
});
|
||||||
|
const bedMap = new Map<number, { total: number; occupied: number }>();
|
||||||
|
for (const bed of allBeds) {
|
||||||
|
if (!bedMap.has(bed.roomId)) bedMap.set(bed.roomId, { total: 0, occupied: 0 });
|
||||||
|
const entry = bedMap.get(bed.roomId)!;
|
||||||
|
entry.total++;
|
||||||
|
if (bed.status === 'occupied') entry.occupied++;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildings,
|
buildings,
|
||||||
rooms: visibleRooms.map((room) => {
|
rooms: visibleRooms.map((room) => {
|
||||||
const occ = occMap.get(room.id) || [];
|
const occ = occMap.get(room.id) || [];
|
||||||
// 计算机构标注
|
|
||||||
const orgs = [...new Set(occ.map((o: any) => o.organization).filter(Boolean))];
|
const orgs = [...new Set(occ.map((o: any) => o.organization).filter(Boolean))];
|
||||||
let orgLabel: string | null = null;
|
let orgLabel: string | null = null;
|
||||||
if (orgs.length > 0 && occ.length > 0) {
|
if (orgs.length > 0 && occ.length > 0) {
|
||||||
const allSameOrg = occ.every((o: any) => o.organization && o.organization === orgs[0]);
|
const allSameOrg = occ.every((o: any) => o.organization && o.organization === orgs[0]);
|
||||||
if (allSameOrg) {
|
orgLabel = allSameOrg ? `均为${orgs[0]}人员` : `存在${orgs.join('、')}人员`;
|
||||||
orgLabel = `均为${orgs[0]}人员`;
|
|
||||||
} else {
|
|
||||||
orgLabel = `存在${orgs.join('、')}人员`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 计算租户颜色:所有住户同一租户则使用该颜色
|
|
||||||
const tenantColors = [...new Set(occ.map((o: any) => o.tenantColor).filter(Boolean))];
|
const tenantColors = [...new Set(occ.map((o: any) => o.tenantColor).filter(Boolean))];
|
||||||
const tenantColor: string | null = tenantColors.length === 1 ? tenantColors[0] : null;
|
const tenantColor: string | null = tenantColors.length === 1 ? tenantColors[0] : null;
|
||||||
// 房间涉及的租户 id(供前端按租赁方筛选)
|
|
||||||
const tenantIds = [...new Set(occ.map((o: any) => o.tenantId).filter(Boolean))];
|
const tenantIds = [...new Set(occ.map((o: any) => o.tenantId).filter(Boolean))];
|
||||||
return {
|
return {
|
||||||
id: room.id,
|
id: room.id,
|
||||||
@@ -263,6 +268,8 @@ export class RoomsService {
|
|||||||
capacity: room.capacity,
|
capacity: room.capacity,
|
||||||
status: room.status,
|
status: room.status,
|
||||||
currentCount: occ.length,
|
currentCount: occ.length,
|
||||||
|
totalBeds: bedMap.get(room.id)?.total ?? 0,
|
||||||
|
occupiedBeds: bedMap.get(room.id)?.occupied ?? 0,
|
||||||
occupants: occ,
|
occupants: occ,
|
||||||
orgLabel,
|
orgLabel,
|
||||||
tenantColor,
|
tenantColor,
|
||||||
|
|||||||
Reference in New Issue
Block a user