พอร์ต taskpane ของ Word add-in "e-Court Documents Versioning" จาก D:\word-addin-poc มาเป็น sibling app ที่ route /docedit/wordaddin: - wordaddin/ (client-only zone): services พอร์ตดิบทั้งชุด (authService/versioningService เป็น mock รอ backend จริง) + models + config - components/wordaddin/: WordAddinGate ฉีด office.js self-host (singleton กัน StrictMode double-inject + fallback timer ครอบเคส onReady เงียบ) และ TaskpaneApp state machine แปลง JSX เป็น Genesis design system - scripts/fetch-office-js.mjs (postinstall ตัวที่สาม): ดาวน์โหลด Office.js runtime ล่าสุดจาก CDN Microsoft ไป public/wordaddin/office-js/ (gitignore สร้างใหม่ได้ — npm @microsoft/office-js ตกรุ่นไม่มี Document.close) - middleware/basePath/root page: เปิด /wordaddin เป็น public (auth แยกจาก แอป note), root /docedit redirect ไป taskpane ยกเว้นลิงก์ e-Court N8 (?fileUrl=&fileId=&typeId=) ที่ยังส่งต่อ /note ตามเดิม - docker/web.Dockerfile รัน fetch-office-js ตอน build + npm scripts dev:https สำหรับ sideload ตาม docs/WORDADDIN-DEV.md Co-Authored-By: Claude Code <noreply@anthropic.com>
274 lines
27 KiB
Markdown
274 lines
27 KiB
Markdown
# Add-in UI Flow — คู่มือสร้าง UI ในโปรเจกต์ใหม่ (รอ API จริง)
|
|
|
|
เอกสารนี้สรุป **เฉพาะฝั่ง UI/flow ของ Word add-in** (`/addin`) ตัดส่วนที่เป็น Office.js mechanics ออกให้เหลือแต่สิ่งที่หน้าตา/พฤติกรรม UI ต้องมี เพื่อเอาไปสร้าง UI ในโปรเจกต์ใหม่แล้ว mock API รอ backend จริง
|
|
|
|
ต้นฉบับอ้างอิง (repo เดิมที่ `D:\word-addin-poc\addin`): `src/taskpane/components/App.tsx`, `ConfirmVersionDialog.tsx`, `VersionHistoryPanel.tsx`, `src/services/versionFlow.ts`, `versioningService.ts`, `metadataService.ts`, `autoSaveService.ts`, `src/models/versionRecord.ts`
|
|
|
|
---
|
|
|
|
## 1. State machine หลัก
|
|
|
|
Task pane ทั้งหมดเป็น state machine เดียว **ไม่มี routing** — component เดียว (`App`) สลับหน้าตาตาม `phase`:
|
|
|
|
```
|
|
checking ──(ไม่มี token)──────────────→ signed-out
|
|
│
|
|
├─(มี token + ไม่ใช่ tracked doc)──→ untracked
|
|
└─(มี token + tracked doc)────────→ tracking
|
|
```
|
|
|
|
การเปลี่ยน phase อื่นๆ:
|
|
|
|
- `signed-out` → (sign in สำเร็จ) → `checking` → ตัดสินใหม่ตามไฟล์ปัจจุบัน
|
|
- `untracked` → (Start Versioning สำเร็จ) → `tracking`
|
|
- ทุก phase → (401 ที่ refresh token ไม่ช่วย / กดออกจากระบบ) → `signed-out`
|
|
- ปุ่ม "ลองใหม่" มีจริงแค่ 2 จุด: `bootError` ใน `checking` และ history error ใน `VersionHistoryPanel` — `untracked`/`tracking` **ไม่มี**ปุ่มลองใหม่แยกต่างหาก (เริ่ม flow ใหม่ด้วยการกดปุ่ม flow ซ้ำ) · กลไกเดียวที่วิ่งกลับมา `checking` คือ sign-in สำเร็จ
|
|
|
|
State ที่ UI ถือนอกเหนือจาก phase:
|
|
|
|
| State | ความหมาย |
|
|
|---|---|
|
|
| `username` | ชื่อผู้ใช้จาก token (แสดงใน header) |
|
|
| `documentId` | tracking id ของเอกสาร (มีเฉพาะ `tracking`) |
|
|
| `documentTemplateId` | template ต้นทางของ**ไฟล์ร่าง** (มีเฉพาะ `untracked` ที่เป็นไฟล์ร่าง — FR-16) |
|
|
| `versions` + `historyLoading` + `historyError` | version history ของ `documentId` |
|
|
| `signingIn`, `loginError` | สถานะหน้า sign-in |
|
|
| `starting`, `saving`, `progressMessage` | busy + ข้อความ progress ของ flow Start Versioning / Save & Version |
|
|
| `confirmOpen`, `startConfirmOpen` | เปิด dialog ยืนยันของ flow ไหน |
|
|
| `flowError`, `flowWarning`, `flowSuccess` | MessageBar 3 แบบของ flow หลัก (แสดงเฉพาะ `tracking`) |
|
|
| `downloadingVersionId`, `downloadingPdfVersionId` | versionId ที่กำลังโหลด docx/pdf (spinner ที่ปุ่ม) |
|
|
| `closeSupported` | platform นี้ปิดเอกสารจาก add-in ได้ไหม — **คุมการแสดงปุ่มปิดเอกสารทุกจุด** (FR-18) |
|
|
| `saveBusy`, `saveStatus`, `saveError` | สถานะชุดปุ่ม [บันทึก] / [บันทึกและปิดเอกสาร] (แยกจาก flow versioning) |
|
|
| `autoSaveError` | banner เตือนเมื่อ auto-save ล้ม (FR-17) |
|
|
| `hasUncommitted` | เคยเห็นการแก้ไขหลัง version ล่าสุด → banner เตือน (FR-3) |
|
|
| `bootError` | error ของการ detect ตอนเปิด task pane (แสดงเฉพาะ `checking` — MessageBar error + ปุ่ม "ลองใหม่" รัน detect ใหม่) |
|
|
|
|
---
|
|
|
|
## 2. UI แต่ละ phase
|
|
|
|
### Header (ทุก phase)
|
|
|
|
- ชื่อแอป: **e-Court Documents Versioning** (semibold)
|
|
- ฝั่งขวา (เมื่อ login แล้วและไม่ใช่ `checking`/`signed-out`): ปุ่มเล็ก icon + `ออกจากระบบ` — username อยู่ใน `title`/`aria-label` (จอแคบ ไม่กินพื้นที่แถบหัว) — กดแล้ว sign out + กลับ `signed-out`
|
|
|
|
### `checking`
|
|
|
|
- `Spinner "กำลังตรวจสอบเอกสาร…"` ตรงกลาง
|
|
- ถ้า detect พัง (`bootError`): MessageBar **error** + ปุ่ม `ลองใหม่` ข้างใน bar (รัน detect ใหม่)
|
|
|
|
### `signed-out`
|
|
|
|
- ข้อความ: "เข้าสู่ระบบเพื่อเริ่มใช้งาน versioning"
|
|
- คำอธิบายจางๆ: "ใช้บัญชีของ realm `word-versioning` — หน้าต่าง login จะเปิดแยกต่างหาก"
|
|
- ปุ่ม primary `เข้าสู่ระบบ` → ระหว่างรันเปลี่ยนเป็น `กำลังเข้าสู่ระบบ…` + disable
|
|
- ถ้า login พัง: MessageBar **warning** (`loginError`) — เช่น "เซสชันหมดอายุ — กรุณา login ใหม่" ตอนโดน 401 กลาง flow
|
|
|
|
> ใน project จริง login เปิดหน้าต่างแยกผ่าน Office Dialog API (ห้าม redirect iframe ไป Keycloak ตรงๆ) — ถ้าโปรเจกต์ใหม่ยังไม่มี auth ให้ mock `login()` คืน `{ ok: true }` ไปก่อน
|
|
|
|
### `untracked`
|
|
|
|
เรียงจากบนลงล่าง:
|
|
|
|
1. ข้อความ: "เอกสารนี้ยังไม่ได้ติดตาม version"
|
|
2. คำอธิบายจางๆ: "กดปุ่มด้านล่างเพื่อเริ่ม track — ระบบจะ stamp metadata ลงไฟล์และอัปโหลดเป็น version 1 (ไฟล์ที่สร้างจากเว็บ portal จะถูกตรวจพบและข้ามขั้นตอนนี้อัตโนมัติ)"
|
|
3. **เฉพาะไฟล์ร่าง** (`documentTemplateId` มีค่า): ข้อความจางๆ เพิ่ม — "ไฟล์นี้เป็นไฟล์ร่างจาก template — กดเริ่มติดตามแล้วระบบจะผูกเอกสารกับ template ต้นทาง และ sync ค่า fields ให้อัตโนมัติ"
|
|
4. ปุ่ม primary `Start Versioning This Document` → ระหว่างรัน: `กำลังเริ่มติดตาม…` + disable
|
|
5. ชุดปุ่มบันทึก `SaveControls` (ดูหัวข้อ 3) — `disabled` เมื่อ `starting`
|
|
6. progress: spinner เล็ก + `progressMessage` (เฉพาะตอน `starting`)
|
|
7. `flowError` → MessageBar **error**
|
|
|
|
### `tracking`
|
|
|
|
เรียงจากบนลงล่าง:
|
|
|
|
1. **เฉพาะ `hasUncommitted`**: MessageBar **warning** — "มีการแก้ไขที่ยังไม่ถูกบันทึกเป็นเวอร์ชันใหม่ — กด Save & Version เพื่อบันทึกเป็น version ใหม่" (FR-3)
|
|
2. `flowWarning` → MessageBar **warning** (เช่น PDF ไม่สำเร็จ / version สำเร็จแต่ปิดเอกสารไม่สำเร็จ)
|
|
3. `flowSuccess` → MessageBar **success** (เช่น "สร้าง version 3 เรียบร้อย")
|
|
4. `flowError` → MessageBar **error**
|
|
5. ปุ่ม primary **ขนาดใหญ่** `Save & Version` — disable เมื่อ `saving`
|
|
6. progress: spinner เล็ก + `progressMessage` (เฉพาะตอน `saving`)
|
|
7. ชุดปุ่มบันทึก `SaveControls` — `disabled` เมื่อ `saving`
|
|
8. บรรทัดจางๆ: `tracking id: {documentId 8 ตัวแรก}…`
|
|
9. `VersionHistoryPanel` (ดูหัวข้อ 5)
|
|
|
|
---
|
|
|
|
## 3. ชุดปุ่มบันทึก `SaveControls` (FR-17/FR-18)
|
|
|
|
Component ใช้ร่วมทั้ง `untracked` และ `tracking` — อยู่นอก flow versioning:
|
|
|
|
- ปุ่ม `บันทึก` — local save เดี่ยว ไม่แตะ metadata ไม่สร้าง version
|
|
- ปุ่ม `บันทึกและปิดเอกสาร` — **เรนเดอร์เฉพาะ `closeSupported === true`** (Word on the web เรียก `close()` ไม่ได้ → ซ่อนทุกแบบ)
|
|
- สถานะตามลำดับ:
|
|
- `saveBusy && status === "saving"` → `Spinner "กำลังบันทึก…"`
|
|
- `!saveBusy && status === "saved"` → text จางๆ `บันทึกแล้ว` — ค้างไว้จนกดบันทึกรอบใหม่ (ไม่ reset เมื่อแก้ไขเอกสาร ไม่มี timeout)
|
|
- `saveError` → MessageBar **error**
|
|
- `autoSaveError` → MessageBar **warning** (default: "บันทึกอัตโนมัติไม่สำเร็จ — ลองกด \"บันทึก\" ด้วยตนเอง")
|
|
- ทั้งสองปุ่ม `disabled` เมื่อ `disabled` (flow versioning กำลังรัน) **หรือ** `saveBusy`
|
|
|
|
พฤติกรรมปุ่ม:
|
|
|
|
| ปุ่ม | Flow |
|
|
|---|---|
|
|
| `บันทึก` | save → สำเร็จตั้ง `saved`, พังโชว์ `saveError` |
|
|
| `บันทึกและปิดเอกสาร` | save → **สำเร็จเท่านั้นจึงปิดเอกสาร** · save พัง = ไม่ปิด + `saveError` (AC-21) · ปิดสำเร็จแล้ว task pane จะปิดตามเอกสารเอง — ไม่มี toast ท้าย flow |
|
|
|
|
---
|
|
|
|
## 4. Dialog ยืนยัน `ConfirmVersionDialog` (FR-2 + FR-13 + FR-18)
|
|
|
|
Dialog เดียวใช้ 2 flow (ปรับข้อความด้วย props):
|
|
|
|
| Prop | Save & Version | Start Versioning |
|
|
|---|---|---|
|
|
| title | "สร้าง version ใหม่?" | "เริ่มติดตามเอกสารนี้?" |
|
|
| description | "ระบบจะบันทึกไฟล์ปัจจุบันและอัปโหลดเป็น version ถัดไปของเอกสารนี้" | ปกติ: "ระบบจะ stamp metadata ลงไฟล์และบันทึกไฟล์ปัจจุบันเป็น version 1 ของเอกสารนี้" · ไฟล์ร่าง: "ระบบจะ stamp metadata ลงไฟล์ บันทึกไฟล์ปัจจุบันเป็น version 1 และผูกเอกสารกับ template ต้นทาง (ไฟล์ร่าง)" |
|
|
| ปุ่มยืนยันหลัก | "สร้าง version" | "เริ่มติดตาม (version 1)" |
|
|
|
|
ภายใน dialog:
|
|
|
|
- ปุ่ม `ยกเลิก` (secondary) มีเสมอ
|
|
- เมื่อมีปุ่มที่สอง ("บันทึกเวอร์ชันและปิดเอกสาร") → ปุ่มนั้นเป็น primary ตัวเดียว และปุ่มยืนยันหลัก ("สร้าง version"/"เริ่มติดตาม") กลายเป็น secondary
|
|
- ช่อง **"คำอธิบาย version (ไม่บังคับ)"** — textarea, `maxLength 2048`, 3 แถว, resize vertical, placeholder "เช่น แก้ไขข้อ 3 สัญญาจ้าง" — ค่าว่างได้ ส่งเป็น optional
|
|
- ปุ่มที่สอง **"บันทึกเวอร์ชันและปิดเอกสาร"** (primary) — เรนเดอร์เฉพาะเมื่อ `closeSupported` **และเฉพาะ flow Save & Version** (Start Versioning ไม่มี)
|
|
- ตอน `busy`: ทุกปุ่ม disable + spinner `กำลังดำเนินการ…` + ปิด dialog ไม่ได้ (ปุ่มยกเลิก/คลิกนอก/ESC ไม่ทำงาน) — สถานะนี้เป็น defense ชั่วขณะก่อน re-render เท่านั้น
|
|
- **กดปุ่มยืนยัน (ตัวใดก็ตาม) → dialog ปิดทันที** แล้ว progress/ผลลัพธ์ (spinner + progressMessage + flow messages) แสดงที่ pane — ไม่ปล่อย dialog ค้างบังผลลัพธ์ (กันกดยืนยันซ้ำสร้าง version ซ้ำ)
|
|
- focus: ตอนเปิดโฟกัสเข้าตัว dialog (container) + Tab ถูกกักไว้ใน dialog + ตอนปิดคืน focus ให้ปุ่ม trigger เดิม
|
|
- เคลียร์ช่อง message ทุกครั้งที่ dialog ปิด (component mount ค้างไว้ตลอด — ต้อง reset ตอน `open` เปลี่ยนเป็น false ไม่ใช่ตอน unmount)
|
|
|
|
---
|
|
|
|
## 5. `VersionHistoryPanel` (FR-4)
|
|
|
|
- Header: icon history + "Version history" (semibold)
|
|
- สถานะ: `loading` → `Spinner "กำลังโหลด…"` · `error` → MessageBar **error** + ปุ่ม `ลองใหม่` · ว่าง (ไม่ loading ไม่ error และไม่มี version) → text จางๆ "ยังไม่มี version ในระบบ"
|
|
- รายการ version เรียง **ใหม่ → เก่า** แต่ละแถว (hover มีพื้นหลัง):
|
|
- `Badge v{versionNumber}` (สี brand)
|
|
- timestamp รูปแบบ medium date + short time ตาม locale
|
|
- `โดย {createdByUserId}`
|
|
- commit message (ถ้ามี) — text 2 สี, `white-space: pre-wrap`, `overflow-wrap: anywhere`
|
|
- ปุ่มขวาแต่ละแถว (เรียงเป็นคอลัมน์):
|
|
- `ดาวน์โหลด` (icon download) → ตอนโหลด **label คงเดิม** สลับ icon เป็น spinner + `aria-busy` (label ยาว "กำลังดาวน์โหลด…" ทำแถว wrap กระโดด)
|
|
- `PDF` (icon pdf) — **แสดงเฉพาะ `hasPdf === true`** → ตอนโหลด label คงเดิม + spinner + `aria-busy`
|
|
- ระหว่างมี version ใดกำลังโหลด → ปุ่มโหลดของ**ทุกแถว** (ทั้ง `ดาวน์โหลด` และ `PDF`) disable กันกดซ้อน
|
|
|
|
---
|
|
|
|
## 6. Flow หลักพร้อมข้อความ progress
|
|
|
|
ข้อความ flow (`flowSuccess`/`flowWarning`/`flowError`) ถูกเคลียร์เป็น null ทุกครั้งที่**เริ่ม** flow ใหม่ (Start Versioning / Save & Version / ดาวน์โหลด — ไม่ใช่ตอนจบ ไม่มี timeout)
|
|
|
|
### Detect (FR-1) — ตอนเปิด task pane / หลัง login / กดลองใหม่
|
|
|
|
1. ตรวจ token — ไม่มี → `signed-out`
|
|
2. อ่าน custom properties ครั้งเดียวทั้งชุด: `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId`
|
|
3. `tracked = (SavedByAddIn === GUID ของ add-in)`:
|
|
- tracked **และมี** `DocumentTrackingId` → `tracking` + โหลด history
|
|
- ไม่ track หรือ track แต่ id หาย → `untracked` (เก็บ `TemplateId` ไว้ใช้ตอน Start Versioning — ไฟล์ร่างอยู่กลุ่มนี้)
|
|
|
|
### Start Versioning (FR-1) — จาก `untracked`
|
|
|
|
dialog ยืนยัน → ข้อความ progress ตามลำดับ:
|
|
|
|
1. `กำลังบันทึก metadata ของเอกสาร…`
|
|
2. `กำลังอัปเดต version history ในไฟล์…`
|
|
3. `กำลังบันทึกไฟล์…`
|
|
4. `กำลังอ่านค่า fields จากเอกสาร…`
|
|
5. `กำลังอัปโหลด version 1…`
|
|
6. `กำลังสร้างสำเนา PDF…` (best-effort — ล้มได้ไม่พัง flow)
|
|
7. `กำลังอัปโหลด version…`
|
|
|
|
> ขั้น 5 กับ 7 ไม่ใช่การอัปโหลดสองครั้ง — ขั้น 5 เป็น marker เข้าสู่ช่วง upload ระหว่างนั้นขั้น 6 ค่อย capture PDF (best-effort) แล้วขั้น 7 คือ POST จริง**ครั้งเดียว**พร้อมแนบ `pdf` ใน multipart เดียวกัน (ตาม `uploadCurrentFile` ของ versionFlow.ts)
|
|
|
|
สำเร็จ → `tracking` + `flowSuccess` = "เริ่มติดตามเอกสารนี้แล้ว — สร้างเป็น version 1" + refresh history · ถ้า `hasPdf === false` → `flowWarning` = "บันทึกแล้ว แต่สร้าง PDF ไม่สำเร็จ — version นี้ไม่มีสำเนา PDF (กด Save & Version อีกครั้งเพื่อสร้าง version ใหม่ที่มี PDF)" · พัง → `flowError` = "เริ่มติดตามเอกสารไม่สำเร็จ"
|
|
|
|
### Save & Version (FR-2) — จาก `tracking`
|
|
|
|
dialog ยืนยัน → progress ทั้ง 7 ขั้นลำดับเดียวกัน ต่างจาก Start Versioning แค่ **2 ข้อความ**: ขั้น 1 `กำลังบันทึก metadata ของ version ใหม่…` (เดิม "…metadata ของเอกสาร…") และขั้น 5 `กำลังอัปโหลด version ใหม่…` (เดิม "…version 1…") — ขั้น 2/3/4/6/7 ใช้ข้อความเดียวกันกับ Start Versioning เป๊ะ
|
|
|
|
หลัง upload สำเร็จ: ถ้าเลข version จริงจาก backend ต่างจากที่ client เดา (มี client อื่นแทรก) → แก้ XML part ในไฟล์ให้ตรง (best-effort ล้มได้เงียบๆ ไม่พัง flow)
|
|
|
|
สำเร็จ → `flowSuccess` = "สร้าง version {N} เรียบร้อย" + refresh history · `hasPdf === false` → warning เดียวกับด้านบน · กดปุ่มที่สอง (บันทึกเวอร์ชันและปิดเอกสาร) → version สำเร็จแล้วจึงปิดเอกสาร — ปิดสำเร็จ UI จบเลย (task pane ปิดตาม) · **ปิดไม่สำเร็จ** → แสดงเป็น **warning ไม่ใช่ error** ("บันทึก version N เรียบร้อย แต่ปิดเอกสารไม่สำเร็จ: … — ปิดเอกสารเองได้เลย") + refresh history ตาม เพราะ version ปลอดภัยบน backend แล้ว · พังก่อน upload → `flowError` = "สร้าง version ไม่สำเร็จ"
|
|
|
|
### Download version (FR-4/FR-7)
|
|
|
|
ปุ่มต่อแถวใน history → ขอ download-link → เปิดใน**เบราว์เซอร์ของเครื่อง** (ห้าม blob download ใน webview ของ Word — ถูกทิ้งเงียบๆ) → `flowSuccess` = "กำลังดาวน์โหลด v{N} ผ่านเบราว์เซอร์ของคุณ — ดูไฟล์ที่โฟลเดอร์ Downloads" หรือ (pdf) "กำลังเปิด PDF ของ v{N} ในเบราว์เซอร์ของคุณ" · พัง → `flowError` = "ดาวน์โหลด version ไม่สำเร็จ"
|
|
|
|
### Auto-save (FR-17)
|
|
|
|
- เริ่มตั้งแต่ mount **ไม่รอ sign-in**
|
|
- periodic ทุก 30 วิ: ไฟล์ dirty → save + ตั้ง flag `hasUncommitted`; สะอาด → ข้าม
|
|
- save ล้ม → `autoSaveError` (warning bar); สำเร็จครั้งถัดไป → เคลียร์ bar
|
|
- ทุก flow versioning ต้อง **suspend** auto-save ตอนเริ่มและ **resume** ตอนจบ (กัน save แทรกกลาง stamp/capture)
|
|
- สร้าง version สำเร็จ → เคลียร์ `hasUncommitted`
|
|
|
|
---
|
|
|
|
## 7. API contract ที่ UI เรียก (จุด mock รอ API จริง)
|
|
|
|
Base URL มาจาก config (dev: `https://localhost:7232`) · ทุก request แนบ `Authorization: Bearer {token}` · backend serialize camelCase
|
|
|
|
### `POST /api/documents/{documentId}/versions` — อัปโหลด version
|
|
|
|
multipart/form-data:
|
|
|
|
| field | type | หมายเหตุ |
|
|
|---|---|---|
|
|
| `file` | Blob (.docx) | ชื่อไฟล์ดึงจาก URL ของ document ถ้าได้ (desktop มักเป็น path จริง — เติม `.docx` ถ้ายังไม่มี) · ดึงไม่ได้ = ไม่ส่งชื่อ ให้ backend ตั้งเอง |
|
|
| `clientVersionId` | string (GUID) | client generate เองก่อน upload |
|
|
| `pdf` | Blob | optional — แนบเมื่อ capture PDF สำเร็จ |
|
|
| `fields` | string (JSON) | optional — map `{ [sdtTag]: text }` จาก content controls |
|
|
| `message` | string | optional — commit message (trim แล้วว่าง = ไม่ส่ง, ≤2048) |
|
|
| `templateId` | string (GUID) | optional — เฉพาะ Start Versioning ของไฟล์ร่าง (FR-16) |
|
|
|
|
response: `{ hasPdf: boolean, versionId: string, versionNumber: number }`
|
|
|
|
### `GET /api/documents/{documentId}/versions` — history
|
|
|
|
response: `VersionRecord[]` เรียงใหม่ → เก่า:
|
|
|
|
```ts
|
|
interface VersionRecord {
|
|
versionId: string;
|
|
versionNumber: number;
|
|
createdAt: string; // ISO-8601
|
|
createdByUserId: string;
|
|
contentHash: string; // SHA256 hex
|
|
message: string | null; // commit message (FR-13)
|
|
hasPdf?: boolean;
|
|
fields: FieldsSnapshot | null;
|
|
}
|
|
// fields เก็บ 2 รูปแบบ: เอกสารผูก template → { [key]: { value, missing } }
|
|
// ไม่ผูก template → { [tag]: text } ดิบๆ — UI ต้องทนทั้งคู่
|
|
// (UI ปัจจุบันไม่ได้แสดง fields — type + helper readSnapshotField เตรียมไว้ใช้ภายหลัง)
|
|
```
|
|
|
|
### `GET /api/documents/{documentId}/versions/{versionId}/download-link[?format=pdf|docx]` — ขอ URL อายุสั้น
|
|
|
|
response: `{ url: string, expiresAt: string }` — UI เปิด `url` ในเบราว์เซอร์เครื่องผู้ใช้เท่านั้น
|
|
|
|
### Auth — OIDC PKCE ผ่าน Office Dialog API
|
|
|
|
`displayDialogAsync` เปิดหน้า login แยก → `messageParent` ส่งผลกลับ task pane · token เก็บ **ใน memory เท่านั้น** (ห้าม localStorage) · refresh token auto-refresh เงียบๆ จน 401 → สถานะ `AuthRequiredError` · mock ช่วงรอ: `login()` คืน `{ ok: true }`, `getAccessToken()` คืน token ปลอม, `getUsername()` คืนชื่อ test
|
|
|
|
> **หมายเหตุสถานะปัจจุบัน (2026-10-14):** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — ทั้ง auth (แทนด้วย mock `wordaddin/services/authService.ts`) และ API ทั้งสามข้างบน (แทนด้วย in-memory mock `wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md`
|
|
|
|
---
|
|
|
|
## 8. กติกา error ที่ UI ต้องรองรับ
|
|
|
|
1. **`AuthRequiredError` (401 ที่ refresh ไม่ช่วย)** — ทุก flow ต้อง catch แล้ว: กลับ `signed-out` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `loginError` = "เซสชันหมดอายุ — กรุณา login ใหม่" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase**
|
|
2. error อื่น → MessageBar ณ จุดที่เกิด (login/history/flow/download แยกกัน) — ห้ามปล่อย unhandled rejection
|
|
3. แยก error ของ "ตัว version" ออกจาก "ปิดเอกสารไม่สำเร็จ" — อันหลังเป็น warning เพราะข้อมูลปลอดภัยแล้ว
|
|
4. PDF เป็น best-effort เสมอ — ล้มแค่เตือน ไม่ fail flow
|
|
|
|
---
|
|
|
|
## 9. ข้อควรระวังที่กระทบหน้าตา UI
|
|
|
|
1. `closeSupported` เป็น flag เดียวที่คุมปุ่ม "บันทึกและปิดเอกสาร" (SaveControls) **และ** ปุ่มที่สองใน ConfirmVersionDialog — Word on the web = false
|
|
2. disable กันกดซ้อน: ปุ่ม flow หนึ่งรัน → ปุ่ม flow อื่น + SaveControls disable ด้วย (แต่ `saveBusy` กับ `saving` แยกกัน — จะไม่ปิดกันเองระหว่าง local save)
|
|
3. ของเดิมใช้ Fluent UI v9 (`@fluentui/react-components` + `@fluentui/react-icons`) — mapping ไป library อื่น: Button / Dialog / MessageBar / Spinner / Badge / Textarea / Text
|
|
4. UI ทั้งหมดภาษาไทย — ข้อความสำเนาได้จากไฟล์ต้นฉบับที่อ้างด้านบน
|
|
5. Task pane แคบ (side panel) — layout เป็นคอลัมน์เดียว ปุ่ม/actions ต้องรองรับความกว้างจำกัด
|