# 平面图 RoomCard：数据结构 ≈ UI 区域

对应代码：`mapPisellReservationRowToFloorRoomCardData` → `FloorRoomCardData` → `ReservationFloorRoomCard`（`getFloorRoomCardType` 决定 `cardType` 与皮肤 class）。占用态金额高亮仍跟 `cardType === 'payed'`；印章 PaymentStamp单独跟 `booking.order.payment_status`（经 `rawToBooking` 写入 `order_payment_status`，仅 `paid` 为已付）。下方示意与真实 class（rc-*）一致；左侧图元、右侧为数据说明。

## 右上角 · 状态来源与枚举

### 宿主 booking / 资源壳（接口原始）

booking.status
  合并为行大类：`hostBookingStatusToCardStatus`
  'occupied' → cardStatus occupied
  'reserved' → booked
  'locked' → maintenance
  空 / 其他 → available
booking.reserved_status
  仅当 `status === 'reserved'` 时写入行 `hostReservedStatus`
  'not_arrived' | 'late'
booking.isTimeout
  仅当 `status === 'occupied'` 时为 `true` → 行 `hostIsTimeout`
booking.order.payment_status
  `rawToBooking` 读 `order.payment_status` → 扁平字段 `FloorRoomCardBooking.order_payment_status`（`paid` 忽略大小写归一）。
  仅印章判断：`=== 'paid'` → 已付样式；其余一律未付。不参与 `cardType` / 是否占用 / `sub_status`。
资源壳 shell（无 bookings 命中时）
  `applyHostResourceShellToBaseRow`：同上字段 + `shell.status`（empty / reserved / occupied / locked）直接改 `cardStatus` 与宿主标记

### 合并行 PisellReservationTableRow

cardStatus
  'occupied' | 'booked' | 'maintenance' | 'barBusy' | 'available'

  → `mapPisellReservationRowToFloorRoomCardData`：
  maintenance → status locked；available → empty；booked → reserved；occupied / barBusy → occupied

hostReservedStatus（预留细分）
  'not_arrived' | 'late'
  兜底：行 `lateMinutes > 0` → sub_status late
占用细分（行级）
  `hostIsTimeout === true` 或 `delayMinutes > 0` → timeout
  `isPaid === false` → payment_required
  否则 → payed

### FloorRoomCardData（图元 props）

status（顶栏标签大类）
  'empty' | 'reserved' | 'occupied' | 'locked'
sub_status（getFloorRoomCardType / 皮肤）
  'not_arrived' | 'late' | 'payed' | 'payment_required' | 'timeout' | ''（空串）
单条 booking 宿主分钟 / 进度
  原始字段 `lateTime`、`timeoutTime`、`progressPercent`（0–100）经 `rawToBooking` 进入 `FloorRoomCardBooking`，优先于本地时间推算
order_payment_status（图元内扁平）
  来自宿主 `booking.order.payment_status`，供 `PaymentStamp` 专用；缺省或无 `paid` 时印章为未付
Next 行（多段 `bookings`）
  `getFloorRoomCardNextNotStartedBooking(data, now)`：`bookings.length ≥ 2`， 且 `bookings[0]` 在当前时刻落在 [start, end)（进行中），且 `bookings[1..]` 中存在 now < start（未开始）→ 取第一条未开始，渲染 `NextBooking`（标签「Next」· 姓名 · `start_time - end_time`）。不依赖 `cardType` 是否为 `late` / `timeout`。标准 `CardBody`（非 compact）末尾展示；约 60s 定时刷新会带动更新。

### 输入 · PisellReservationTableRow（合并行）

    id, code, main_field
    cardStatus → 映射 status / 分支
    capacityLabel → capacity 文案
    hostReservedStatus, lateMinutes → reserved.sub_status
    hostIsTimeout, delayMinutes, isPaid → occupied.sub_status
    floorMapSourceBookings[] → rawToBooking → bookings[]

→

### 中间 · FloorRoomCardData

    main_field, capacity
    status: empty | reserved | occupied | locked
    sub_status: not_arrived | late | payed | payment_required | timeout
    bookings: FloorRoomCardBooking[]
    Next 行: bookings[0] 进行中 ∧ 后方有未开始
      → getFloorRoomCardNextNotStartedBooking(data, now)

→

### 每条 booking（rawToBooking）

    start_date, start_time, end_date, end_time
    number, amount
    holder.name, holder.phone
    lateTime, timeoutTime, progressPercent（宿主分钟/进度）
    order.payment_status → order_payment_status（印章）

## 预留 · cardType = not_arrived / late（rc--reserved）

test 1 Cap: 100
已预订
肉肉6666 ***8745
🕐 16:53 开始 · 👥 2 · 🕐 还有 8 分钟
⚠ +15m
① 顶栏 rc-header data.main_field → 房名
data.capacity + i18n「Cap」
data.status → StatusTag（已预订）
② 联系人 rc-contact bookings[0].holder.name
holder.phone（脱敏后四位）
③ 时间行 rc-time-row booking.start_time + i18n「开始」
booking.number（人数）
倒计时：resolveFloorRoomCardReservedCountdownMinutes（优先 `remainingReserveTime` 分钟，否则本地起止推算；仅 not_arrived）
④ 预留迟到 · rc-highlight--overtime 仅 cardType = late 时显示「+10m」
优先 booking.lateTime（分钟）→ format 文案
否则 getFloorRoomCardLateTime(start_date, start_time)

## 占用 · cardType = payed / payment_required / timeout（rc--occupied）

test 1 Cap: 100
使用中
肉肉6666 ***8745
🕐 17:03 结束 · 👥 2
⚠ late 12m
$ 10.50
已付
Next·下一位客人·19:00 - 21:00
① 顶栏 同预留：main_field、capacity、status → 使用中
② 联系人 bookings[0].holder.*
③ 时间行 booking.end_time + i18n「结束」
booking.number
④ 占用超时 · rc-highlight--danger 仅 cardType = timeout 时「late 10m」
优先 booking.timeoutTime（分钟）
否则 getFloorRoomCardLateTime(start)
⑤ 金额 rc-amount booking.amount
isPaid（cardType payed）→ 高亮 class
⑥ 进度条 ProgressBar 宽度 % ← booking.progressPercent（0–100）
无则用起止时间 resolveFloorRoomCardProgressPercent
isTimeout → rc-progress__fill--warn 黄色
⑦ 印章 PaymentStamp 仅看 `booking.order_payment_status === 'paid'`（数据来自宿主 `booking.order.payment_status`，`rawToBooking` 里 trim + 大小写归一为 `paid`）。
已付 / 未付与 cardType（payed / payment_required / timeout）解耦。
⑧ Next 行 rc-next `getFloorRoomCardNextNotStartedBooking(data, Date.now())` 非空时渲染。
条件：`bookings[0]` 当前在时段内；`bookings[1+]` 中第一条 `now < start`。
文案：`holder.name`（示例「下一位客人」）+ `start_time - end_time`。预留 / 占用标准体均可能出现；compact 行不展示。

### 宿主 booking 根字段 → 行 / 图元（摘要）

status | empty | reserved | occupied | locked → cardStatus → FloorRoomCardData.status
reserved_status | not_arrived | late → row.hostReservedStatus → sub_status（与 lateMinutes 兜底）
isTimeout | true → row.hostIsTimeout → occupied sub_status = timeout
lateTime（分钟） | raw → booking.lateTime → 预留 late 时「+10m」类（优先于本地推算）
timeoutTime（分钟） | raw → booking.timeoutTime → 占用 timeout 时「late 10m」类（优先于本地推算）
progressPercent | 0–100 → booking.progressPercent → ProgressBar 宽度（优先于起止时间推算）
order.payment_status | 行合并：参与列表侧 `isPaid` → 占用行 `sub_status` 为 `payed` / `payment_required`（见 `mapPisellReservationRowToFloorRoomCardData`）。 单条 booking：`rawToBooking` → `FloorRoomCardBooking.order_payment_status` → 仅 `PaymentStamp`：`paid` 已付，其余未付。
Next 行 | 多段 `bookings`：`bookings[0]` 进行中（`now ∈ [start, end)`）且其后存在未开始（`now < start`）→ `getFloorRoomCardNextNotStartedBooking` → `NextBooking`（Next · 姓名 · `start_time - end_time`）。与 `late`/`timeout` 无关；时段由 `start_date/start_time/end_date/end_time` 解析。