# NormalProductDetailModal（普通商品详情弹窗）

基于 `ProSkuDetailModal`（`SkuDetailModal`）封装的普通商品 SKU 详情弹窗。**不依赖** Booking 的 `Context`，通过 `ref.init` 打开；**确认结果只通过 props `onConfirm` 返回**（不再使用 `init.callback`）。

本封装不使用 `skuDetailConfig`；关闭、移除分别用 `onClose`、`onRemove`。

## 从包中引入

```ts
import {
  NormalProductDetailModal,
  type NormalProductDetailModalRef,
  type NormalProductDetailModalProps,
  type NormalProductDetailModalOnConfirmPayload,
} from '@pisell/private-materials';
```

## 基本用法

1. 在页面（或布局）中挂载一次组件。
2. 使用 `useRef<NormalProductDetailModalRef>(null)` 拿到实例。
3. 调用 `ref.current?.init(payload)` 打开弹窗。
4. 在 `onConfirm` 中根据 `payload.mode` 区分 `edit` / `create`。

```tsx
import React, { useRef } from 'react';
import {
  NormalProductDetailModal,
  type NormalProductDetailModalRef,
  type NormalProductDetailModalOnConfirmPayload,
} from '@pisell/private-materials';

export function Example() {
  const modalRef = useRef<NormalProductDetailModalRef>(null);

  const openEdit = () => {
    modalRef.current?.init({
      mode: 'edit',
      cacheItem: {
        /* 与 SkuDetailModal.open 一致的商品行 */
      },
      onClose: () => {
        // 可选：弹窗关闭时清理外部临时状态
      },
    });
  };

  const handleConfirm = (payload: NormalProductDetailModalOnConfirmPayload) => {
    if (payload.mode === 'edit') {
      console.log('编辑确认', payload.data);
      return;
    }
    console.log('新增确认', payload.formattedLine, payload.detail);
  };

  return (
    <>
      <button type="button" onClick={openEdit}>
        打开普通商品详情
      </button>
      <NormalProductDetailModal
        ref={modalRef}
        onConfirm={handleConfirm}
        onRemove={(productData) => {
          /* 弹窗内移除 */
        }}
        onClose={() => {
          /* 每次关闭（含取消），在 init.onClose 之后 */
        }}
      />
    </>
  );
}
```

## `ref.init(payload)`

类型为 `NormalProductDetailModalInitPayload`（见 `types.ts`）。

### 编辑模式 `mode: 'edit'`

| 字段 | 说明 |
|------|------|
| `mode` | `'edit'` |
| `cacheItem` | 当前编辑的商品行（传给 `SkuDetailModal.open`） |
| `onClose` | 可选 |

### 新增模式 `mode: 'create'`

| 字段 | 说明 |
|------|------|
| `mode` | `'create'` |
| `productId` / `date` / `isEject` / `productData` | 与原先 create 打开参数一致 |
| `onClose` | 可选 |

### 其他

不带 `mode` 时，将 `payload` 原样交给 `SkuDetailModal.open`（兼容历史用法）；确认时仍走 **`onConfirm({ mode: 'edit', data })`** 分支（与编辑相同的处理逻辑）。

## `onConfirm` 载荷

- **`{ mode: 'edit', data }`**：`data` 为已做 `getProductTotalPrice`、`setProductPrice` 等处理后的行数据。
- **`{ mode: 'create', formattedLine, detail }`**：`formattedLine` 为 `formatDataKey` 后的对象；`detail` 为完整明细（语义对齐旧版 create 的 callback 首参与第三参）。

## 关闭 / 移除

- **关闭**：先执行 `init.onClose`（若有），再执行 props `onClose`。
- **移除**：仅 props `onRemove`。

## 与 Booking 内 `EditNormalService` 的区别

| 项目 | `NormalProductDetailModal`（Plus） | `booking/.../editNormalService` |
|------|-----------------------------------|----------------------------------|
| Booking Context | 不使用 | 使用 |
| 确认出口 | 仅 `onConfirm` | `callback` / `dispatch` / `onCartChange` 等 |

预约单内嵌场景请继续使用 `EditNormalService`。
