32 KiB
แผน N9 — กลุ่มเอกสารตาม typeId จาก e-Court + หน้ารายการเอกสารในกลุ่ม
สถานะ: implement แล้ว (2026-09-23) —
tsc+ vitest 142/142 + Playwright ผ่านทุกข้อ verify ของ task 3–4 + plan-reviewer/code-reviewer ไม่พบ violation (คงเหลือ e2e ลิงก์ e-Court จริงตามแผน ตอนปิดงาน N8) — เขียนใหม่ 2026-09-22 แทนร่างเดิม "Document Set จัดชุดมือใน UI" (2026-09-17, commit6940b38) ตามคำขอผู้ใช้: ให้ e-Court บอกกลุ่มผ่านลิงก์แทนการจัดชุดด้วยมือ เพิ่มเติม (2026-09-23 ตามคำขอผู้ใช้): การ์ดไฟล์หน้า/groups/[typeId]หุบ/กางดู bookmark+annotation ทั้งไฟล์ (ปิด gap 5.3.10 ครึ่งหลังที่เคยเป็น out of scope) + ย่อข้อความหมายเหตุเหลือ "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้"Redesign (2026-09-24 ตามคำขอผู้ใช้): หน้า
/groups/[typeId]เปลี่ยนจากการ์ดต่อไฟล์เป็น explorer 2 คอลัมน์ ไม่มี navbar — แผงตัวกรองฝั่งซ้าย (ไฟล์ / ชนิดรายการ bookmark|annotation / ชนิดของ Annotation / ผู้บันทึก) + list เดียวรวม bookmark+annotation ทั้งกลุ่มไม่แยกไฟล์ (รายละเอียด D3) — ผ่าน client componentscomponents/groups/3 ตัว เพิ่มเติม (2026-09-23 ตามคำขอผู้ใช้): ฟอร์มอัปโหลด N4 เพิ่มช่อง "กลุ่ม typeId" ทางเลือก (ไม่กรอก = ไม่มีกลุ่ม) —uploadFileกลายเป็นจุดเขียนtype_idที่สองนอกจาก N8 (insert ตอนสร้าง row ใหม่เท่านั้น normalize เหมือน N8: trim → ว่าง = NULL, cap 200) — ยังไม่มี UI แก้กลุ่มของไฟล์ที่มีอยู่เดิม เลขฟีเจอร์: ร่างเดิม (2026-09-17) เคยใช้เลข N8 — ยกให้แผนลิงก์ e-Court (docs/PLAN-ECOURT.md) ซึ่งเป็น dependency ต้นทาง แผนนี้จึงเป็น N9 Dependency: N8 — การรับ/refreshtypeIdของเอกสารจากลิงก์เกิดที่registerExternalFileของ N8 ทั้งหมด (เดิมแผนนี้ไม่มี code path ที่เขียนtype_idเอง — ต่อมาเพิ่มช่องกลุ่มของฟอร์มอัปโหลด N4 ตามคำขอผู้ใช้ 2026-09-23) — N8 implement ครบแล้ว (2026-09-22 รวม D5): schematype_id+ index มากับ migrationdrizzle/0002ด้วย ตาม governance ของโปรเจกต์: implement ต้องเขียนสเปกลงdocs/PLAN-FEAT.md§7 (N9) + ขยาย whitelist ในCLAUDE.md+ อัปเดตdocs/PLAN.md§3 (schema) ก่อน — เป็น task แรกของแผน แล้วรันผ่าน/implement-feat N9
Context
- กรณีจริงของศาล: คดีหนึ่งงวด = หลายเอกสาร (คำฟ้อง / คำพิพากษา / คำสั่งศาล) — ผู้ใช้อยากเห็นเอกสารที่เกี่ยวเนื่องกันจัดกลุ่มมาแสดง และมีหน้ารวมทุกเอกสารของกลุ่ม
- ร่างเดิม (2026-09-17) ออกแบบเป็นตาราง
document_sets+ ย้ายไฟล์เข้า/ออกด้วยมือใน UI — ผู้ใช้ตัดสินใจใหม่ (2026-09-22): ให้ e-Court ส่งรหัสกลุ่มมากับลิงก์ (?typeId=— เปลี่ยนชื่อจาก caseId ตามคำขอ) จึงไม่ต้องมีตารางแยก / Server Actions / UI จัดการกลุ่มเลย เหลือแค่ column + การแสดงผล - ข้อจำกัดที่ยอมรับ (ผู้ใช้ยืนยัน 2026-09-22): ระบบเรารู้จักเฉพาะเอกสารที่เคยเปิดผ่านลิงก์ e-Court (row เกิดตอน
registerExternalFile) — หน้ากลุ่มจึงแสดงเฉพาะเอกสารที่รู้จักแล้ว (เคยมีหมายเหตุ "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้" กำกับ — ถอดออกตามคำขอผู้ใช้ 2026-09-24; รายการครบทั้งคดีอยู่ใน e-Court ไม่ใช่ระบบเรา)
การตัดสินใจ (ยืนยันกับผู้ใช้แล้ว 2026-09-22)
- กลุ่มระดับเดียว กำหนดด้วย
typeIdจากลิงก์ e-Court — เพิ่ม column บนตารางfilesค่าเดียว (type_id— ถอดtype_nameตามคำขอผู้ใช้ 2026-09-22, แสดงผลด้วยรหัสกลุ่มโดยตรง) — ไม่มีตารางกลุ่มแยก ไม่มี many-to-many ไม่มีหลายระดับ (เอกสารหนึ่งอยู่กลุ่มเดียวตามธรรมชาติของงาน) — ความหมายของ id เป็นของ e-Court ฝั่งเราถือว่า "ส่งมาเหมือนกัน = กลุ่มเดียวกัน" - เข้ามาอัตโนมัติ ไม่มี UI จัดการกลุ่ม — ไฟล์ได้กลุ่มเมื่อเปิดผ่านลิงก์ที่มี
typeId(เขียนที่ N8) หรือกรอกช่อง "กลุ่ม typeId" ตอนอัปโหลด N4 (เพิ่ม 2026-09-23) — กลุ่มเกิด/หายตามข้อมูลจริงในfilesไม่มีสถานะกลุ่มค้างในตารางอื่น ไม่มีหน้าทางย้ายไฟล์ที่มีอยู่เดิมเข้า/ออกกลุ่ม - หน้ารายการ
/จัดกลุ่มตามtypeId(section ต่อกลุ่ม + ส่วน "ไม่มีกลุ่ม" ท้ายสุด) + หน้าใหม่/groups/[typeId]แสดงเอกสารทั้งกลุ่มที่รู้จัก พร้อมหมายเหตุกำกับ (ถอดออก 2026-09-24 ตามคำขอผู้ใช้) และการ์ดแต่ละไฟล์หุบ/กางดู bookmark+annotation ของไฟล์นั้นได้ (2026-09-23 — รูปแบบเปลี่ยนเป็น explorer ใน redesign 2026-09-24)
Design
D1 — Schema (db/schema.ts + drizzle/)
// files เพิ่ม (เอกสารจากลิงก์ e-Court / อัปโหลด N4 ที่กรอกกลุ่ม — ไฟล์ seed และอัปโหลดที่ไม่กรอก = NULL เสมอ):
typeId: text("type_id"),
// + index ("files_type_id_idx") บน type_id
- ไม่มี FK/ตาราง
document_sets(ต่างจากร่างเดิม) — กลุ่ม = ค่า distinct ของtype_idณ ตอน query - migration:
/sync-db(push) +npx drizzle-kit generate— rule data-layer: Docker migrate จาก SQL ในdrizzle/เท่านั้น; ไฟล์ generated idempotent (IF NOT EXISTS) - การเขียน
type_idสองที่: N8registerExternalFile(insert ครั้งแรก + update เมื่อลิงก์รอบใหม่ส่งtypeIdมา — ดู PLAN-ECOURT.md D1) และuploadFileของ N4 ช่องกลุ่มทางเลือก (insert ตอนสร้าง row ใหม่เท่านั้น — เพิ่ม 2026-09-23) — แผนนี้ (N9) อ่านอย่างเดียว - ไม่มี DTO/mapper ใหม่ — หน้าที่ใช้เป็น server component ทั้งหมด ข้อมูลข้ามไป client เป็น plain object
{id, title, typeId}(ถ้าต้องส่งcreatedAtข้าม boundary = ISO string ตาม invariant #5)
D2 — หน้ารายการ / จัดกลุ่ม (app/page.tsx)
- query
filesเหมือนเดิม → แบ่งเป็น section ต่อกลุ่ม (type_idไม่ NULL) เรียงตามcreatedAtของไฟล์ล่าสุดในกลุ่ม (กลุ่มที่มี activity ใหม่อยู่บน) — การ์ดไฟล์ในกลุ่มเรียงcreatedAtเหมือนเดิม + ปุ่ม Annotate เดิม - หัวกลุ่ม =
type_id+ จำนวนไฟล์ + ลิงก์ "เอกสารในกลุ่ม" →/groups/{encodeURIComponent(typeId)}(icon Folder) - ไฟล์ที่
type_idNULL (seed / อัปโหลด N4 ที่ไม่กรอกกลุ่ม / ลิงก์แบบไม่มี typeId) → section "ไม่มีกลุ่ม" ไว้ท้ายสุด (ใกล้ฟอร์มเพิ่มเอกสาร) — แสดงเสมอถ้ามีไฟล์ - ไม่มี client component ใหม่ ไม่มี action ใหม่ — จัดกลุ่มเป็นการแสดงผลล้วนบน server component เดิม
D3 — หน้า /groups/[typeId] แสดงเอกสารในกลุ่ม (app/groups/[typeId]/page.tsx)
Server component force-dynamic (login ถูกบังคับโดย middleware.ts อยู่แล้ว — path ใหม่เข้า net อัตโนมัติ):
- โหลด:
files where type_id = params.typeIdorderBy createdAt(Next decode dynamic param ให้อัตโนมัติ — อย่าเรียกdecodeURIComponentซ้ำ ตาม edge case #5) — ไม่มี = 404 (กลุ่มที่ระบบไม่รู้จัก/ย้ายไฟล์ออกหมดแล้ว) - แสดง: หัวกลุ่ม (
type_id+ "N เอกสาร") + ลิงก์กลับ/ หมายเหตุกำกับทุกครั้ง: "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้"— ถอดออกตามคำขอผู้ใช้ 2026-09-24 (เดิมเป็นกล่อง info ใต้หัวกลุ่ม ย่อจากข้อความ "… — อาจไม่ครบทุกฉบับของกลุ่ม" 2026-09-23)- รายการเอกสาร: ชื่อไฟล์ + วันที่นำเข้า (
formatThaiDate) + จำนวน annotation/bookmark + ปุ่ม/ลิงก์ Annotate →/annotate/{fileId}(ไม่ต้อง deep link หน้า — เข้าไปเหมือนปกติ) การ์ดแต่ละไฟล์หุบ/กางดู bookmark+annotation ทั้งไฟล์ (คำขอผู้ใช้ 2026-09-23 — แถบ summary เป็น en: "Annotations & Bookmarks (N)")→ แทนด้วย explorer 2 คอลัมน์ (redesign 2026-09-24 ตามคำขอผู้ใช้): ไม่มี navbar (ถอดAppNav— เหลือ back link + หัวกลุ่ม + หมายเหตุ):- ซ้าย = แผงตัวกรองเป็น tree menu 2 ระดับสไตล์ Quasar docs (
q-tree--dense q-tree--no-connectors— รอบสอง 2026-09-24 ตามคำขอผู้ใช้, ธีม Genesis พื้นสว่างตามคำยืนยัน) — desktop aside sticky 15rem scroll ภายใน; มือถือ<lgหุบเป็น<details>"ตัวกรอง (N)" pattern เดียวกับหน้าสรุป (nested details ทำงานได้): 4 กลุ่ม (ไฟล์ — "ทุกไฟล์" + รายไฟล์พร้อมไอคอนชนิดไฟล์ + จำนวน แม้ 0 เพื่อกด "เปิดเอกสารนี้" ได้ / ชนิดรายการ Bookmark+Annotation — checkbox คู่ ไม่เลือก = แสดงทั้งหมด (default) ติ๊กอะไรเห็นเฉพาะสิ่งนั้น (direct selection ตามคำขอผู้ใช้ 2026-09-24 รอบสอง — แบบแรกเปิดครบทั้งคู่ต้องปิดเอง = สับสน; เคยย้ายไป segmented บนหัว content แล้วย้ายกลับมาอยู่ในแผงตามคำขอผู้ใช้) / ชนิดของ Annotation 7 ค่า label TYPE_SHORT — กติกาเดียวกับชนิดรายการ: ไม่เลือก = ทุกชนิด (default) ติ๊กอะไรเห็นเฉพาะชนิดนั้น (คำขอผู้ใช้ 2026-09-24), ติ๊กชนิดแล้วเลือก Annotation ในชนิดรายการให้อัตโนมัติ / เลิกเลือก Annotation แล้วล้างชนิดที่ติ๊กค้างไว้เริ่มใหม่ตาม default (คำขอผู้ใช้ 2026-09-24), แสดงเฉพาะชนิดที่มี ≥1 รายการ, HIGHLIGHT รวม 4 สไตล์เป็นค่าเดียว / ผู้บันทึก — "ทุกคน" + รายคน label จากuserName || "ไม่ทราบชื่อ"เรียง localeCompare th) = parent<details open>หุบ/กางอิสระ + chevron หมุน 90° (patternbm-toggleของต้นไม้ bookmark N2), leaf =<button>dense ไอคอน + ชื่อ + badge pill จำนวน — เลือก button แทน label+input เพราะโดนกฎ coarse 44px ของmain.listing-pageใน globals.css พอดี (desktop dense ~28px / จอสัมผัส 44px อัตโนมัติ — ไม่ต้องแต้ globals.css; แถวต้องมีลูกกลาง flex-1 กัน justify-content:center ของกฎ), active ผ่านaria-pressed:(แถบซ้ายน้ำเงิน +bg-primary/[0.08]+ ตัวน้ำเงิน, border-l โปร่งคงไว้ทุกแถวกัน layout shift — design review 2026-09-24 ถอด font-medium กันตัวอักษรกระตุกตอน toggle), multi-select (ชนิดรายการ/ชนิดของ Annotation) มีกล่อง checkbox visual 14px หน้าไอคอน (คำขอผู้ใช้ 2026-09-24) (เลือก = พื้นน้ำเงิน + ✓ ขาว — ยังเป็น visual ของ<button aria-pressed>ไม่ใช่ native input, กฎ coarse 44px ยังครบ; design review 2026-09-24: filter แบบ facet ใช้ checkbox convention ต่างจาก nav highlight เดี่ยวของ Quasar — เดิมรอบแรกเคยเป็น ✓ ขวามือ), ไม่ได้ติ๊ก Annotation (ชนิดรายการ) = กลุ่มชนิด disabled + หุบ + หรี่ทั้งกลุ่ม (คำขอผู้ใช้ 2026-09-24 — กรองชนิดมีความหมายเฉพาะเมื่อดู annotation; ติ๊ก Annotation แล้วกลุ่มกลับมาใช้ได้+กางให้อัตโนมัติ ผ่าน propopenของ<details>ที่เขียน DOM เฉพาะตอนค่าเปลี่ยน การหุบ/กางด้วยมือจึงไม่ถูกเขียนทับ), ชนิด annotation reuseAnnotationTypeIconผ่านTYPE_ICON_DATA(data ตัวแทนค่าว่าง), ผู้บันทึกไม่มีไอคอนใช้ spacer คง indentation, facet count เป็น static จากข้อมูลทั้งกลุ่ม (ไม่หดตามตัวกรองอื่น) - ขวา = list เดียวรวม bookmark+annotation ทั้งกลุ่มไม่แยกไฟล์ เรียงตามลำดับอ่านสำนวน: ไฟล์ตาม
createdAt→ หน้า → หน้าเดียวกันให้ที่คั่นหน้าก่อนหมายเหตุ (ไล่ตามตำแหน่งในเอกสาร) →createdAttiebreak — sort ครั้งเดียวบน server ฝั่ง client กรองเป็น pure predicate; แต่ละแถวเป็น passive surface — จุดคลิกเดียวคือปุ่ม<Link>"View" ท้ายแถว (คำขอผู้ใช้ 2026-09-24: ไม่กดทั้งแถว — เดิมเป็น Link เต็มแถว) deep link เดียวกับหน้าสรุป F12 (?ann=<id>/?page=N) +title/aria-labelบอกปลายทางรายแถว + รูปแบบแถว/ไอคอน/โน้ต/ป้าย "จัดการแล้ว"/"ใน: …" ของ sub bookmark ตามหน้าสรุป F15 + ไฟล์ที่มาต้น meta line พร้อมไอคอนชนิดไฟล์ (truncate เฉพาะ span ของตัวเอง — ไม่กลืน หน้า/ผู้บันทึก/วันที่) + วันเวลาที่สร้าง (formatThaiDatewithTime) ในบรรทัด meta - header ของคอลัมน์ content: "พบ N รายการ" + ปุ่ม "เปิดเอกสารนี้" (เฉพาะกรองไฟล์เดียว — ทางเข้าไฟล์ที่ไม่มีรายการใด ๆ, ตัดสินใจเก็บไว้ตามคำยืนยันผู้ใช้ 2026-09-24) + "ล้างตัวกรอง" (เฉพาะมีตัวกรอง active — ชนิดรายการที่ติ๊กไว้นับเป็น 1 ตัวกรอง)
- empty state 3 แบบ: กลุ่มไม่มีรายการเลย / กรองจนไม่เหลือ (+ ปุ่มล้าง) / เลือกไฟล์เดียวที่ไม่มีรายการ (+ ปุ่มเปิดเอกสารนี้)
- สถาปัตยกรรม:
page.tsxquery เต็ม 2 query (inArray fileId+orderByหน้า) แล้วจัดเป็นGroupFileSummary[]+GroupListItem[]ส่งให้ client component เดียวGroupExplorer(state ตัวกรองก้อนเดียว +useMemoกรอง/นับ facet) แยก presentational เป็นFacetFilterPanel(แผงตัวกรองกลางcomponents/explorer/ใช้ร่วมกับหน้าสรุป — ย้ายจาก FilterControls 2026-09-24)/GroupList— read-only ล้วน ไม่มี Server Action/SSE ใหม่
- ซ้าย = แผงตัวกรองเป็น tree menu 2 ระดับสไตล์ Quasar docs (
- ไม่มี SSE (snapshot ณ ตอนโหลด เหมือนหน้าสรุป F15) ไม่มีปุ่มแก้ไข/ย้ายไฟล์ (จัดการที่ e-Court ฝั่งเดียว)
generateMetadataตั้งชื่อแท็บ browser ตามชื่อกลุ่ม (pattern เดียวกับหน้าสรุป F15)
D4 — สิทธิ์/การเข้าถึง
- ใครก็ที่ login เห็นได้ทุกกลุ่ม (mock login — roles ยัง out of scope ตามแผนหลัก)
- ไม่มี Server Action ใหม่ → ไม่มี publish SSE /
revalidatePathของแผนนี้ (การเปลี่ยนกลุ่มเกิดที่ N8 ตอน register แล้ว redirect ออกจาก/ทันที — ไม่มี Router Cache ค้าง)
Docs/rules sync (plan-first — ทำเป็น task แรก ก่อนแตะโค้ด)
| ไฟล์ | สิ่งที่แก้ |
|---|---|
docs/PLAN.md §3 |
เพิ่ม column files.type_id (text NULL + index — ไม่มีตารางกลุ่ม/ไม่มีคอลัมน์ชื่อกลุ่ม) + files.external_id (ค้างจาก N8 — ทำพร้อมกันตอน sync 2026-09-23) |
docs/PLAN-FEAT.md §7 |
หัวข้อใหม่ N9 · กลุ่มเอกสารตาม typeId + หน้า /groups/[typeId] (format ตาม N5–N7 + อ้างแผนฉบับเต็ม docs/PLAN-DOCSET.md) |
CLAUDE.md |
whitelist ฟีเจอร์เสริมหลังแผน เพิ่ม N9 + โครงสร้างไฟล์เพิ่ม app/groups/[typeId]/page.tsx |
.claude/rules/data-layer.md |
เพิ่ม: files.type_id เขียนที่ N8 registerExternalFile เท่านั้น (ลิงก์ไม่ส่ง = คงเดิม) — ไม่มี action/SSE ใหม่ของ N9 |
docs/requirement/gap-analysis.md5.3.10 ครึ่งหลัง ("หรือชุดของเอกสาร") — เดิมแผนนี้ไม่ปิด (แสดงเฉพาะ เอกสาร รวมกลุ่ม) แต่ปิดแล้ว 2026-09-23 ด้วยการหุบ/กางดู bookmark+annotation ต่อไฟล์บนหน้ากลุ่ม (D3).claude/rules/realtime.md,pdf-viewer.md,coordinates.mdไม่แตะ
ลำดับ task + verify (ทุก task: npx tsc --noEmit ผ่าน)
| # | งาน | ไฟล์หลัก | Verify เพิ่มเติม |
|---|---|---|---|
| 1 | Docs sync N9 | ตารางด้านบน | สเปก §7 ครบตาม format N5–N7; PLAN.md §3 มี column ใหม่ |
| 2 | Schema + migrate — ทำแล้วกับ N8 (type_id + files_type_id_idx อยู่ใน db/schema.ts + migration drizzle/0002 idempotent + dev DB ยืนยันด้วย psql 2026-09-23) |
db/schema.ts, drizzle/ |
ตรวจแล้วผ่าน: psql \d files เห็น type_id + index (ไม่มี type_name) |
| 3 | หน้า / จัดกลุ่ม |
app/page.tsx |
ทดสอบด้วยลิงก์จำลอง (การ์ด EcourtLinkForm / path /samples/… + typeId — N8 พร้อมแล้ว) หรือ update files set type_id ตรง ๆ ก็ได้: จัด section ถูก / "ไม่มีกลุ่ม" ท้ายสุด; regression: อัปโหลด N4 + Annotate ยังทำงาน |
| 4 | หน้า /groups/[typeId] |
app/groups/[typeId]/page.tsx |
รายการครบถูกลำดับ + หมายเหตุแสดง + นับ annotation/bookmark ตรง + กรองไฟล์เดียวแล้วกด "เปิดเอกสารนี้" เปิดไฟล์ได้ (redesign 2026-09-24 — เดิมปุ่ม Annotate ต่อการ์ด); typeId ไม่รู้จัก = 404; ชื่อแท็บ browser ตามกลุ่ม |
| 5 | ปิดงาน | — | plan-reviewer + code-reviewer + vitest + tsc; e2e เต็ม (ลิงก์ e-Court จริง) ทำตอนปิดงาน N8 |
Edge cases (ออกแบบไว้ล่วงหน้า)
- ลิงก์รอบใหม่ส่ง
typeIdคนละค่า → ย้ายกลุ่มตามค่าล่าสุด (เกิดที่ N8) — annotation/bookmark ผูกกับfile_idไม่เกี่ยวกับกลุ่ม จึงตามไฟล์ไปเอง - ลิงก์รอบใหม่ไม่ส่ง
typeId→ ไฟล์คงกลุ่มเดิม (N8: ห้ามเคลียร์ค่าเดิม) - ไฟล์ไม่เคยมี
typeId(seed / อัปโหลด N4 ที่ไม่กรอกกลุ่ม / ลิงก์เก่า) → section "ไม่มีกลุ่ม" บนหน้า/, ไม่มีหน้ากลุ่ม - กลุ่มแสดงผลด้วยรหัสล้วน — ไม่มีคอลัมน์ชื่อกลุ่ม (ถอด
type_nameตามคำขอผู้ใช้ 2026-09-22) — ถ้าภายหลังต้องการชื่อกลุ่ม กลับมาเพิ่ม column ใหม่ตาม governance typeIdมีอักขระพิเศษ/ยาว → ผ่าน URL ด้วยencodeURIComponent; normalize ที่ N8 (trim → ว่าง = NULL, cap 200); Next decode ให้อัตโนมัติตอนอ่านparams- กลุ่มว่างลง (ไฟล์ย้ายออกหมด) → กลุ่มหายจากหน้า
/เอง และ/groups/[typeId]กลายเป็น 404 (query จากfilesจริง ไม่มีตารางกลุ่มค้าง) - Dev DB แชร์กับการทดสอบของผู้ใช้ → เทสหน้า UI ด้วยการตั้ง
type_idบน id ที่จับเป๊ะ ไม่ reset ข้อมูลทั้งกลุ่ม - N8 ยังไม่ implement ตอนทำ N9 →
verify หน้า UI ด้วยการเขียน DB ตรง (task 3–4) แล้วรอ e2e จริงตอนปิดงาน N8(ล้าสมัย — N8 implement ครบแล้ว 2026-09-22: verify ได้ด้วยลิงก์จำลอง/การ์ดEcourtLinkForm+ path/samples/…ทันที) typeIdมี/(เช่น เลขคดีไทยด2024/2567) → ลิงก์/groups/…%2F…— Next 14 route ได้ (match บน raw path ก่อน decode) แต่ reverse proxy บางตัว (Tomcat/JBoss ปฏิเสธ%2Fโดย default) อาจ 404 ทั้งที่กลุ่มมีจริง — known risk ที่รับไว้ (code-reviewer 2026-09-23): ทดสอบ e2e กับ deployment จริงของ e-Court ตอนปิดงาน N8 ถ้าเจอปัญหาแก้ที่จุดสร้างลิงก์จุดเดียว (replace/ด้วยตัวอักขระปลอดภัยตอน encode) หรือเปลี่ยนเป็น query param
Out of scope (ไม่ทำในแผนนี้)
- จัดกลุ่ม/แก้ชื่อ/ลบกลุ่มด้วยมือใน UI ของเรา (ทิ้งจากร่างเดิม — กลุ่มมาจาก e-Court อย่างเดียว)
- ไฟล์อยู่หลายกลุ่ม (many-to-many) / กลุ่มซ้อนหลายระดับ
bookmark รวมทั้งกลุ่ม— ทำแล้ว (2026-09-23): การ์ดไฟล์หน้ากลุ่มหุบ/กางดู bookmark+annotation ต่อไฟล์ (D3; รูปแบบเปลี่ยนเป็น list รวมกลุ่มละแถวบอกไฟล์ที่มา ใน redesign 2026-09-24)- export JSON ตามกลุ่ม (N7 ยังรับ
fileIdเดียว) - API ดึงข้อมูลให้ระบบภายนอก = N10
docs/PLAN-API.md(แผนแยก) - รายการเอกสารครบทุกฉบับของคดีรวมฉบับที่ยังไม่เคยเปิด (ข้อมูลอยู่ที่ e-Court)
Verification (ภาพรวม end-to-end — หลัง N8 เสร็จ)
npm run dev→ login → เปิดลิงก์ e-Court 2 ไฟล์ที่typeIdเดียวกัน + 1 ไฟล์typeIdต่างกลุ่ม → หน้า/จัด 2 section ตามกลุ่ม + section "ไม่มีกลุ่ม" (ไฟล์ seed/อัปโหลดที่ไม่กรอกกลุ่ม); อัปโหลดไฟล์พร้อมกรอกกลุ่ม → การ์ดไปอยู่ section ของกลุ่มนั้น + หน้า/groups/[typeId]เห็นไฟล์อัปโหลดด้วย- หัวกลุ่ม → "เอกสารในกลุ่ม" →
/groups/[typeId]: เห็นไฟล์ครบตามลำดับ (หมายเหตุถูกถอด 2026-09-24) + จำนวน annotation/bookmark ต่อไฟล์ - หน้า
/groups/[typeId]→ เห็น list เดียวรวม bookmark+annotation ทั้งกลุ่ม (แถวบอกไฟล์ที่มา) + กดแถวแต่ละรายการเด้งไปตำแหน่งนั้นใน viewer (?ann=/?page=); ตัวกรอง 4 กลุ่มไล่ได้ครบ — เลือกไฟล์เดียว/เฉพาะ bookmark/เฉพาะชนิด annotation/ของคนใดคนหนึ่ง แล้วจำนวน "พบ N รายการ" ลดตาม + "ล้างตัวกรอง" กลับมาครบ (redesign 2026-09-24; เดิมเป็นการ์ดต่อไฟล์กาง<details>"Annotations & Bookmarks (N)") - กด Annotate → เข้า viewer ของไฟล์นั้นได้ตามปกติ
- เปิดลิงก์รอบใหม่ของไฟล์เดิมด้วย
typeIdคนละค่า → ไฟล์ย้ายกลุ่ม กลุ่มเดิมลดจำนวน/หายไปเมื่อว่าง - Regression: อัปโหลด N4 / annotate / ค้นหา N6 / export JSON N7 ยังทำงานเหมือนเดิม