# bookingChangeModal 接入说明

`bookingChangeModal` 是一个以 `Promise` 形式调用的预约变更弹窗，不需要先在页面里渲染 `<BookingChangeModal />`。

## 1. 基本调用

```ts
import bookingChangeModal from './index';

await bookingChangeModal
  .open({
    item,
    targetCourtId: item.courtId,
    targetDate: item.date,
    targetHour: item.startHour,
  })
  .then((result) => {
    console.log('submit success', result);
  })
  .catch((reason) => {
    console.log('modal closed', reason);
  });
```

## 2. 推荐接入方式

通常你会在打开时同时传入：

- `item`: 当前 booking
- `courts` / `visibleCourts`: 场地列表
- `bookings`: 当前日期下的 booking 列表，用于冲突判断
- `hourSlots`: 可选时间槽
- `getBookingDetailData`: 顶部客户信息展示
- `hasConflict`: 判断目标时间是否冲突
- `onSubmit`: 点击确认后，在弹窗内部发起提交

示例：

```ts
await bookingChangeModal.open({
  item,
  targetCourtId: moveConfirm.targetCourtId,
  targetDate: moveConfirm.targetDate,
  targetHour: moveConfirm.targetHour,
  courts,
  visibleCourts: courts,
  bookings,
  hourSlots,
  firstTimelineHour: 8,
  endTimelineExclusive: 20.5,
  getBookingDetailData: (bookingItem, courtList) => ({
    customer: {
      name: bookingItem.name || '',
      phone: bookingItem.phone || '',
      email: '',
    },
    headerSummary: {
      customerName: bookingItem.name || '',
      resourceLabel:
        courtList.find((court) => court.id === bookingItem.courtId)?.name || '',
    },
  }),
  hasConflict: (allBookings, targetCourtId, targetDate, startHour, endHour, excludeId) => {
    return allBookings.some((booking) => {
      if (booking.id === excludeId) return false;
      if (booking.courtId !== targetCourtId || booking.date !== targetDate) return false;
      return !(endHour <= booking.startHour || startHour >= booking.endHour);
    });
  },
  onSubmit: async ({ submitPayload }) => {
    return request('/shop/v1.1/order/appointment/child-booking/96305', {
      method: 'POST',
      data: submitPayload,
    });
  },
});
```

## 3. `open` 主要参数

### 必传

```ts
{
  item: BookingChangeModalItem;
  targetCourtId: string;
  targetDate: string;
  targetHour: number;
}
```

### 常用可选参数

- `courts`: 全量场地列表
- `visibleCourts`: 当前可选场地列表；如果传了，会优先用于下拉展示
- `bookings`: 用于冲突判断的 booking 列表
- `hourSlots`: 时间槽数组，例如 `[8, 8.5, 9, 9.5]`
- `firstTimelineHour`: 最早可选时间
- `endTimelineExclusive`: 最晚结束时间上限
- `selectedCustomer`: 自定义初始客户信息
- `formatHourLabel`: 自定义时间格式化函数
- `isBusinessHour`: 过滤可选时间槽
- `getBookingDetailData`: 生成顶部客户和资源展示信息
- `hasConflict`: 冲突判断函数
- `onCustomerChange`: 点击顶部 `Change` 时触发，可异步返回新的客户信息
- `onSubmit`: 点击确认后在弹窗内部执行提交
- `submitErrorMessage`: 提交失败时的兜底错误文案
- `theme`: `'dark' | 'light'`
- `title` / `description` / `confirmText` / `cancelText`: 自定义文案

## 4. `onSubmit` 的调用时机

点击确认按钮后，弹窗内部会：

1. 先根据当前 `item + draft + targetCourt` 生成 `submitPayload`
2. 如果没传 `onSubmit`，直接 `resolve`
3. 如果传了 `onSubmit`，则 `await onSubmit(context)`
4. 提交成功后关闭弹窗并 `resolve`
5. 提交失败时保留弹窗，并在底部显示错误信息

## 5. `onSubmit` 收到的上下文

```ts
type BookingChangeModalSubmitContext = {
  item;
  original;
  draft;
  customer;
  duration;
  nextEndHour;
  changed;
  submitPayload;
  options;
};
```

其中最常用的是：

- `draft`: 当前用户修改后的目标值
- `submitPayload`: 组件内部组装好的提交数据

## 6. 返回值

`open()` 返回一个 Promise。

### 成功时 `resolve(result)`

```ts
{
  item,
  original,
  draft,
  customer,
  duration,
  nextEndHour,
  changed,
  submitPayload,
  submitResult,
}
```

说明：

- `submitPayload`: 弹窗内部构造的提交参数
- `submitResult`: `onSubmit` 的返回值；如果没传 `onSubmit`，则没有这个字段

### 关闭时 `reject(reason)`

```ts
{
  type: 'cancel' | 'backdrop' | 'escape' | 'programmatic-close' | 'replaced';
  message?: string;
}
```

## 7. `submitPayload` 说明

组件会尽量基于现有数据构造出接近下面结构的 payload：

```ts
{
  id,
  relation_id,
  like_status,
  relation_type,
  is_all,
  schedule_id,
  sub_type,
  select_date,
  number,
  metadata,
  resources,
  start_date,
  start_time,
  end_date,
  end_time,
  duration,
}
```

字段来源优先级大致为：

1. `item`
2. `item.metadata` / `item.resources` / `item.raw`
3. 目标 `court`
4. 默认兜底值

所以如果你希望提交数据更接近真实接口，建议在 `item` 和 `courts` 里尽量补齐：

- `relation_id`
- `form_id`
- `resource_id`
- `relation_type`
- `metadata.form_name`
- `metadata.resource_name`

## 8. Demo

可参考：

- `packages/private-materials/src/plus/bookingChangeModal/demo.tsx`

当前 demo 里包含了多个场景：

- 默认打开
- 拖拽目标预设
- 改日期
- 浅色主题
- 冲突态
- 自定义文案
- 无客户切换

