commit 6fd0c3174f07bd63394373cc05796436d9768cc7 Author: DESKTOP-1R2VSQH\Lenovo ThinkPad E490 Date: Thu Sep 10 14:21:21 2026 +0700 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..efe6b04 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# Jude's Note — PDF Annotation สำหรับ e-Court + +ระบบ annotate ไฟล์ PDF คดีบนเว็บ แบบ **Non-Destructive** — ไฟล์ PDF ต้นฉบับไม่ถูกแก้ไขเด็ดขาด annotation ทั้งหมด (highlight / เส้นวาด / comment / bookmark) เก็บใน PostgreSQL เป็นพิกัด % ของขนาดหน้า แล้วซิงก์ระหว่างทุก browser แบบ realtime ผ่าน **SSE (Server-Sent Events)** + +สร้างด้วย Next.js 14 (App Router) + TypeScript strict + pdfjs-dist v4 + Drizzle ORM + PostgreSQL (Docker container เดียว) + +## ฟีเจอร์ + +- **Highlight** — คลุมดำข้อความ เลือกสีจาก floating toolbar (5 สี) วาดซ้อนหลังข้อความด้วย `mix-blend-mode: multiply` +- **Freehand draw** — วาดเส้นอิสระบน SVG layer ความหนาสัมพัทธ์กับขนาดหน้า ไม่เบี้ยวเวลา zoom +- **Sticky note comment** — คลิกวาง pin บนหน้า พร้อม popover ข้อความ/ผู้เขียน/เวลา +- **Bookmark** — จดตำแหน่ง (หน้า + % ความสูง) กดแล้ว smooth-scroll กลับตำแหน่งเดิม +- **Realtime sync** — เปิด 2 แท็บ วาดในแท็บ A แท็บ B เห็นทันทีผ่าน SSE (`EventSource` ต่อใหม่อัตโนมัติเมื่อขาด) +- **Page Virtualization** — render เฉพาะหน้าใน viewport ± 1 หน้า ไฟล์หลายสิบหน้าเลื่อนลื่น +- ไม่มีระบบ login — anonymous identity เก็บใน `localStorage` (ปุ่มลบแสดงเฉพาะรายการของตัวเอง) + +## สิ่งที่ต้องมี (Prerequisites) + +| เครื่องมือ | เวอร์ชันที่ใช้พัฒนา/ทดสอบ | หมายเหตุ | +|---|---|---| +| Node.js | v24.3.0 | ≥ 20 น่าจะใช้ได้ | +| Docker | 29.1.3 | ใช้รัน PostgreSQL container ตัวเดียว | + +## ขั้นตอน Setup จากศูนย์ + +### 1. ติดตั้ง dependencies + +```bash +npm install +``` + +> postinstall จะคัดลอก `pdf.worker.min.mjs` จาก `node_modules/pdfjs-dist/build/` มาไว้ที่ `public/` ให้อัตโนมัติ + +### 2. รัน PostgreSQL container + +```bash +docker run -d --name judes-note-db \ + -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres \ + -p 54322:5432 \ + -v judes-note-db-data:/var/lib/postgresql/data \ + postgres:17-alpine +``` + +ครั้งต่อไปแค่ `docker start judes-note-db` — ข้อมูลอยู่ใน volume `judes-note-db-data` ไม่หาย + +### 3. สร้างไฟล์ `.env` + +```env +DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/ecourt-note +``` + +(เทียบกับ `.env.example` — มีแค่ตัวเดียว) + +### 4. สร้างตารางในฐานข้อมูล + +```bash +npx drizzle-kit push +``` + +### 5. Seed ไฟล์ตัวอย่าง + +ไฟล์ตัวอย่างอยู่ที่ `public/samples/` แล้ว — ลงทะเบียนในตาราง `files` (รันเฉพาะเมื่อตารางว่าง ไม่งั้นข้อมูลจะซ้ำ): + +```bash +docker exec judes-note-db psql -U postgres -c "insert into files (title, pdf_url) values ('01012-672427-4f-690609-0000840227.pdf','/samples/01012-672427-4f-690609-0000840227.pdf'), ('01012-672427-1f-690609-0000840223.pdf','/samples/01012-672427-1f-690609-0000840223.pdf'), ('dashboard-report-2026-09-03.pdf','/samples/dashboard-report-2026-09-03.pdf');" +``` + +### 6. รัน dev server + +```bash +npm run dev +``` + +เปิด http://localhost:3000 — คลิกไฟล์เพื่อเข้าหน้า annotate + +## การใช้งานบนหน้า annotate + +| ปุ่ม | พฤติกรรม | +|---|---| +| ลูกศร (select) | คลุมดำข้อความ → เด้ง toolbar เลือกสี → ติด highlight บนหน้า | +| ดินสอ (draw) | ลากเมาส์วาดเส้นอิสระ (ปล่อย = บันทึก) | +| ลูกศร+กล่องแชท (comment) | คลิกจุดบนหน้า → พิมพ์ข้อความ → บันทึก pin | +| bookmark+ | เพิ่ม bookmark ที่ตำแหน่งกึ่งกลางจอปัจจุบัน | +| zoom − / + | สเกล 50%–300% (annotation ทั้งหมด scale ตามเพราะเก็บเป็น %) | + +Sidebar ซ้าย: แท็บ Bookmarks / Annotations — คลิกรายการเพื่อเลื่อนกลับไปตำแหน่งนั้น และมีปุ่มลบเฉพาะรายการของตัวเอง (anonymous identity จาก `localStorage`) + +ทดสอบ realtime: เปิดหน้า annotate ไฟล์เดียวกันใน 2 แท็บ แล้ววาด/comment จากแท็บหนึ่ง — อีกแท็บเห็นทันทีโดยไม่ต้อง refresh + +## Troubleshooting + +| อาการ | สาเหตุ/ทางแก้ | +|---|---| +| Realtime ไม่ซิงก์ | sync ใช้ pub/sub **in-memory ของ process เดียว** — ทุกแท็บต้องเข้าผ่าน dev server เดียวกัน (ถ้าวันหนึ่งรันหลาย instance ต้องเพิ่ม Redis pub/sub หรือ Postgres LISTEN/NOTIFY ก่อน) | +| หน้าเว็บ 500 หลังรัน `next build` ระหว่างที่ dev server กำลังรัน | build เขียนทับ cache ของ dev — ปิด dev server ก่อน build หรือ restart dev server หลัง build | +| ต่อ DB ไม่ได้ | ตรวจว่า container รันอยู่ (`docker start judes-note-db`) และพอร์ต 54322 ไม่ถูกใช้โดย container อื่น | +| Highlight ใช้ไม่ได้กับไฟล์คดีตัวอย่าง | ไฟล์ `01012-*.pdf` ทั้งสองเป็น**ภาพสแกนล้วน** (ไม่มี text layer) — ใช้ `dashboard-report-2026-09-03.pdf` ซึ่งเป็น text-based PDF แทน จึงจะคลุมดำได้ (DRAW/COMMENT/BOOKMARK ใช้ได้ปกติกับทุกไฟล์) | + +## หมายเหตุทางเทคนิค + +- **Realtime = SSE + pub/sub in-process:** Server Actions (`app/actions/annotations.ts`) publish event หลังเขียน DB สำเร็จทุกครั้ง → `lib/annotate-events.ts` → SSE endpoint `app/api/annotations/stream` → hook เปิด `EventSource` รับ event (payload เป็น DTO จาก server ตรง ๆ) +- UI ใช้ `useOptimistic` ที่มาจาก React canary build ซึ่ง Next.js 14 vendored ไว้ให้ — หากหยิบ component ไปรันนอก Next bundling (เช่น unit test ด้วย `react` เปล่า) จะหา export นี้ไม่เจอ +- พิกัด annotation ทุกชนิดเก็บเป็น **% ของขนาดหน้า** → render ถูกต้องทุก zoom/ขนาดจอ และ `mergeRectsByLine` รวบ rect ให้เหลือ 1 อันต่อบรรทัดก่อนบันทึก highlight +- สถาปัตยกรรม/สเปกฉบับเต็ม: `docs/PLAN.md` · กติกาที่ต้องรักษาตอนแก้โค้ด: `CLAUDE.md` · ฟีเจอร์ต่อยอด: `docs/PLAN-FEAT.md` + +## โครงสร้างหลัก + +``` +db/schema.ts ตาราง + JSONB strict union + DTO + toDTO +db/index.ts postgres.js + drizzle (prepare: false) +lib/annotate-events.ts pub/sub in-process (actions → SSE route) +utils/coordinates.ts px ⇄ % + clamp + mergeRectsByLine +utils/user.ts anonymous identity (localStorage) +hooks/useAnnotationRealtime.ts EventSource รับ SSE event + dedupe ด้วย id +app/actions/annotations.ts Server Actions CRUD + publish event (ทางเขียนทางเดียว) +app/api/annotations/stream/route.ts SSE endpoint (GET ?fileId=) +app/page.tsx รายการไฟล์ +app/annotate/[id]/page.tsx โหลด file + initial data → PDFViewer +components/pdf/ PDFViewer (root + virtualization), InkLayer, HighlightToolbar +components/sidebar/ AnnotationSidebar +public/samples/ ไฟล์ PDF ตัวอย่าง +```